创建缓冲区溢出错误的测试用例(C / c ++)

时间:2010-07-15 06:06:37

标签: c unit-testing automated-tests

如何在c中创建一个单元测试用例,用于缓冲区溢出,不会导致内存错误,例如段错误?

即。给出一个简单的缓冲区溢出,例如

int function () {
    int exampleArray[10];
    exampleArray[10] = 5;
    return 0;
}

如何为此代码创建单元测试?显然有一个错误,我们正在编写一个数组的末尾。但是,您可以在没有任何错误证据的情况下运行这样的函数。

注意:我需要能够为用户在运行时提供数组索引以及上述简化案例时创建测试用例。

在像Java这样的托管语言中,代码会抛出一个可以捕获的异常(ArrayIndexOutOfBoundsException)。因此创建一个测试用例很简单(例外的try-catch块)。

如何在c中创建这样的测试? C的任何单元测试框架都可以处理这种情况吗?

背景信息:我正在尝试自动生成测试用例。我知道错误的位置,并希望能够创建一个单元测试来对这些错误进行失败。

但是我不知道如何创建一个测试用例,该测试用例因缓冲区溢出错误而无法使应用程序崩溃。

8 个答案:

答案 0 :(得分:4)

检查的一种方法是在缓冲区之前和之后分配额外的字节,如果您正在使用堆。但要跟踪每个变量都很困难。函数结束后,您可以检查这些缓冲区中的数据是否被修改。您必须创建一个单独的库来为您保存这些值。

或者选中此link。希望它能为您提供有关缓冲区溢出测试的更多信息。

编辑:更多信息:

  1. 测试不接受任何输入的API很难或者更不是你的工作。但是,如果API接受将在执行过程中操作的输入,则可以传递可能导致溢出的值。

    void foo()
    {

    char buffer [5];
    
    
     strcpy(buffer, "StackOverflow");
    
    // Clearly overflow. Has to be found out in Code reviews or static analysis
    
    
    }
    

答案 1 :(得分:3)

已经指出捕捉每个缓冲区溢出很难。在我之前的项目中,编译器没有像manneorama指出的选项。幸运的是,整个代码库从未直接调用malloc,每个malloc调用的代码指南(严格执行)必须调用一个函数,默认情况下用于调用malloc(让我们将其命名为mymalloc)。

所以我们所做的就是创建额外的缓冲区大小,比如说4个字节额外(在请求的内存之前和之后2个。用字节填充它们,你希望代码不会写入(这就是解决方案不是声音的地方) )

在顶部头文件中定义一个宏:

#define mymalloc(X) testmalloc(X,__FILE__,__LINE__)

并定义一个函数testmalloc,如下所示:

void * testmalloc(size_t size, char *filename, int linenum)
{
   void *buff = malloc(size + 4)
   char *bytebuff = (char *) buff
   //bookkeeping, keep record of buff
   bytebuff[0] = 0xBA;
   bytebuff[1] = 0xBA;

   bytebuff[size+2] = 0xBA;
   bytebuff[size+3] = 0xBA;

   return bytebuff[2];
}

(这段代码来自我的记忆,我没有实际的代码,所以它可能会有轻微的错误)

每当你必须检查溢出时,只需编写一个程序,遍历所有这些缓冲区并检查字节。 __FILE____LINE__预处理宏用于跟踪程序的哪一行导致溢出。

这并不能确保所有溢出都被捕获为:

  • 程序可能会溢出完全相同的字节(此处为0xBA)
  • 程序可能会保持保护字节不变但溢出超出范围

这对我们来说很容易,因为高度规范的代码库使这段代码花了几分钟的时间,我们发现了一些有趣的错误。

此机制也仅限于堆分配的数组。

答案 2 :(得分:2)

您没有指定平台,但GNU C库有一个名为__malloc_hook的变量,它是static void *my_malloc_hook(size_t, const void *)形式的函数指针。

一个例子:(取自here


static void *
my_malloc_hook (size_t size, const void *caller)
{
      void *result;
      /* Restore all old hooks */
      __malloc_hook = old_malloc_hook;
      __free_hook = old_free_hook;
      /* Call recursively */
      result = malloc (size);
      /* Save underlying hooks */
      old_malloc_hook = __malloc_hook;
      old_free_hook = __free_hook;
      /* printf might call malloc, so protect it too. */
      printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
      /* Restore our own hooks */
      __malloc_hook = my_malloc_hook;
      __free_hook = my_free_hook;
      return result;
}

使用这个你可以分配比必要更多的内存(你需要以某种方式跟踪)并用一些预定义的标记值填充该空间,将指针返回到空间中。然后,当然,您必须在代码中的适当位置插入边界检查,这可能不是您想要的。希望这至少可以提供一些想法。

答案 3 :(得分:1)

你只能尝试检测对零下方和最后一个元素正上方的索引的访问 - 比如分配两个额外的数组并用标记值填充它们,然后检查值。

但是,您无法轻松检测到C中的所有缓冲区溢出。您的代码可以访问远离缓冲区边界的元素,例如静默地损坏堆。或者在多线程程序中,一个线程可以意外地覆盖另一个线程的堆栈。内存访问在C中不受限制 - 如果地址映射到地址空间并且可编写,程序可以在那里写入,则无法轻易检测到。

答案 4 :(得分:1)

您提供的代码不是单元可测试的。您需要控制需要测试的内容:参数和返回值。

如果你真的想用C来说明其他语言的作用,想象一下像以下那样的函数:

int setArrayInt(int *array, unsigned int arraySize, unsigned int index, int value)
{
  if (index >= arraySize)
  {
    return -1; /* error */
  }

  array[index] = value;
  return 0; /* success */
}

然后您就可以对此函数进行单元测试(cxxtest语法):

/* Function returning failure if accessing out of range index */
TS_ASSERT_EQUALS(-1, setArrrayInt(exampleArray, sizeof(exampleArray), 10, 123));

/* Function returning success if we stay inside its range */
TS_ASSERT_EQUALS(-1, setArrrayInt(exampleArray, sizeof(exampleArray), 9, 123));
/* Our array really gets modified if our call is successful */
TS_ASSERT_EQUALS(123, exampleArray[9]);

最后,您将使用此函数来操作数组,因为您已经验证它可以正常工作。

我并不是说你应该在你的C程序中创建这样的数组操作函数。为了捕捉您在示例中给出的那种错误,您肯定需要静态分析或代码检测工具。

答案 5 :(得分:0)

如果您在英特尔上运行,则可以在Valgrind内运行代码。它有一个实验缓冲区溢出检测器,所以YMMV。我想不出一种简单的方法来检测C本身内的缓冲区溢出,而不需要添加大量的内容并检测保护变量。

答案 6 :(得分:0)

您应确保所有缓冲区写入函数都具有已定义的缓冲区大小,无论是变量还是某个常量。

例如,如果你有

void writeSomeString(char * buf, size_t len);

你可以测试它是否符合这样的缓冲区大小:

char testBuf[100];
memset(&testBuf, 42, sizeof(testBuf));
writeSomeString(&testBuf, sizeof(testBuf)-1);
assert(testBuf[sizeof(testBuf)-1] == 42);

答案 7 :(得分:0)

在您的示例中,只能使用静态分析工具(如 Klocwork http://www.klocwork.com)或 Lint ({{3})来测试局部变量分配})或类似的。

但是,如果你为了简单[1]将数组封装在一个类型(typedef int MY_ARRAY[10])中,你可以做以下几点:

static union
{
  MY_ARRAY fixed_array;
  unsigned int dummy_array_overlay[sizeof(MY_ARRAY)+1];
} array_layout;

#define myarray             array_layout.fixed_array
#define dummy_array_overlay array_layout.dummy_array_overlay

memset(&dummy_array_overlay, 0xFF, sizeof(dummy_array_overlay));

//Do your thing

ASSERT_EQUAL((unsigned int)0xFF, dummy_array_overlay[sizeof(MY_ARRAY)]);

注意联合如何帮助在同一偏移处布局数组和底层0xFF测试值。

[1]通常认为封装这样的数组是一个坏主意 - 使用嵌入数组的结构