Создание контрольного примера для ошибки переполнения буфера (C / c ++) - PullRequest
5 голосов
/ 15 июля 2010

Как создать блок модульного теста в c для переполнения буфера, которое не вызывает ошибку памяти, такую ​​как segfault?

т.е. Учитывая простое переполнение буфера, например

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

Как создать модульный тест для этого кода? Там явно ошибка, мы пишем после конца массива. Однако вы можете запустить такую ​​функцию без каких-либо доказательств ошибки.

Примечание: мне нужно иметь возможность создавать тестовые случаи, когда индекс для массива предоставляется пользователем во время выполнения, а также упрощенный случай, описанный выше.

В управляемом языке, таком как Java, код генерирует исключение (ArrayIndexOutOfBoundsException), которое может быть перехвачено. Таким образом, создать тестовый пример просто (блок try-catch для исключения).

Как создать такой тест в c? Может ли какая-либо из структур модульного тестирования для C справиться с такой ситуацией?

Справочная информация: я пытаюсь выполнить автоматическую генерацию тестовых случаев. Я знаю, где находятся ошибки, и хотел бы иметь возможность создать модульное тестирование, чтобы оно не сработало.

Однако у меня не было бы ни малейшего представления о том, как создать тестовый пример, который завершается ошибкой из-за ошибки переполнения буфера, которая не приводит к падению приложения.

Ответы [ 8 ]

4 голосов
/ 15 июля 2010

Один из способов проверки - выделить дополнительные байты до и после буфера, если вы используете кучу.Но будет трудно отслеживать каждую переменную.После завершения функции вы можете проверить, были ли изменены данные в этих буферах.Вы должны создать отдельную библиотеку для хранения этих значений для вас.

В качестве альтернативы проверьте эту ссылку .Надеюсь, что это даст вам больше информации о тестировании на переполнение буфера.

РЕДАКТИРОВАТЬ: Еще немного информации:

  1. Трудно или скорее не ваша работа для тестирования API, которыепринять любой вклад.Однако если API принимает ввод, которым будут манипулировать в ходе выполнения, вы можете передавать значения, которые могут вызвать переполнение.

    void foo()
    {

    char buffer [5];
    
    
     strcpy(buffer, "StackOverflow");
    
    // Clearly overflow. Has to be found out in Code reviews or static analysis
    
    
    }
    
3 голосов
/ 15 июля 2010

как уже отмечалось, перехват каждого переполнения буфера затруднен. В моем предыдущем проекте у компилятора не было опций, подобных указанным manneorama. К счастью, вся база кода никогда не вызывала malloc напрямую, правила кода (строго соблюдаемые) при каждом вызове malloc должны вызывать функцию, которая по умолчанию использовалась для вызова malloc (давайте назовем ее mymalloc).

Итак, мы создали дополнительный размер буфера, скажем, 4 дополнительных байта (2 до и 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 голосов
/ 15 июля 2010

Вы не указываете платформу, но в библиотеке GNU C есть переменная с именем __malloc_hook, которая является указателем на функцию static void *my_malloc_hook(size_t, const void *).

Пример: (взято из здесь )


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;
}

Используя это, вы можете выделить немного больше памяти, чем необходимо (что вам нужно будет отслеживатькаким-то образом) и заполните это пространство некоторым предопределенным значением маркера, возвращая указатель некоторым образом в пространство.Затем, конечно же, вам придется вставлять проверки границ в соответствующие места в вашем коде, что может быть не тем, что вы хотите.Надеюсь, что это дает хотя бы некоторые идеи.

1 голос
/ 16 июля 2010

Код, который вы представили, не может быть проверен юнитом.Вы должны контролировать то, что вам нужно тестировать: параметры и возвращаемые значения.

Если вы действительно хотите проиллюстрировать на 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]);

Наконец, вы будете использовать эту функцию для манипулирования массивами, потому что вы проверили, что она работает правильно.

Я не имею в виду, что вы должны создавать такие функции манипулирования массивами в своих программах на Си.Чтобы уловить ошибки, которые вы привели в примере, вам определенно нужны инструменты статического анализа или инструментального кода.

1 голос
/ 15 июля 2010

Вы можете только попытаться обнаружить доступ к индексам прямо под нулем и прямо над последним элементом - например, выделить два дополнительных массива и заполнить их значениями маркера, а затем проверить значения.

Вы не можете легко обнаружить все переполнения буфера в Си. Ваш код может получить доступ к элементу далеко за пределами буфера и, например, молча испортить кучу. Или в многопоточной программе один поток может случайно перезаписать стек другого потока. Доступ к памяти неограничен в C - если адрес сопоставлен с адресным пространством и доступен для записи, ваша программа может писать в него, вы не можете легко это определить.

0 голосов
/ 02 октября 2012

В вашем примере тестирование присвоений локальных переменных может выполняться только с помощью инструментов статического анализа, таких как Klocwork (http://www.klocwork.com) или, возможно, Lint (http://en.wikipedia.org/wiki/Lint_(software))) или аналогичный.

Но, если вы для простоты [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] Обычно считается плохой идеей инкапсулировать массив таким образом - вместо этого используйте структуру, встраивающую массив

0 голосов
/ 15 июля 2010

Вы должны убедиться, что все ваши функции записи в буфер имеют определенный размер используемого ими буфера, будь то по переменной или по некоторой константе.

Например, если у вас есть

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);
0 голосов
/ 15 июля 2010

Если вы работаете на Intel, вы можете запустить код внутри Valgrind . У него есть экспериментальный детектор переполнения буфера, так что YMMV. Я не могу придумать простой способ обнаружения переполнения буфера в самом C без добавления большого количества ошибок для определения и обнаружения защитных переменных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...