Есть ли утилита динамической проверки, которая может пометить переполнение статического буфера в структуре?

Существует ли утилита динамической проверки, которая может пометить следующую ошибку? Вальгринд не может. Можно ли очистить или застраховать ++? Это последняя версия Linux Ubuntu.

struct A {
    char buff1[8];
    int jj;
    char buff2[8];
    int ii;
    char buff3[8];
} a;

main(int argc, char *args[])
{
    // Set intermediate fields to known flag value
    a.ii = a.jj = 0xdeadbeef;

    // Write 8 char string into 8 byte buffer - null will overflow into neighboring int field. ERROR
    sprintf(a.buff2, "ABCDEFGH");
}

4 ответа

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

Без серьезного нарушения выравнивания и целостности конструкции это невозможно сделать в середине конструкции.

РЕДАКТИРОВАТЬ: Еще один момент: есть конструкции, где запись за границы элементов структуры совершенно законно и единственная разумная возможность достичь того, что вы хотите. Одним из примеров является копирование структур в кучу:

struct x orig, *copy;

orig.a = 100;
strcpy (orig.str, "Test");

copy = malloc (sizeof (struct x));
memcpy (copy, &orig, sizeof (struct x));

Это также записывает за пределы элементов структуры, но это единственный разумный (и совершенно законный) способ поместить структуру в кучу (кроме утомительного и медленного копирования для членов).

Другой пример будет

p = malloc (NUM_STRUCTS * sizeof (struct x));
memset (p, NUM_STRUCTS * sizeof (struct x), 0);

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

В некотором смысле даже calloc() будет писать за пределами структурных элементов....

И, как определенный ответ из (по общему признанию более старого) Руководства пользователя Purify, я случайно обнаружил в одном из своих ящиков стола:

Purify обнаруживает ошибки границ массивов в массивах в структурах C, только когда доступ выходит за пределы всей структуры

Это считается "нет" для меня.

Это должен быть динамический инструмент? Единственный известный мне инструмент, который обнаружит описанный выше сценарий, - это Coverity, статический инструмент из Synopsis. Для приведенного выше случая будет подготовлен отчет в форме:

Error: OVERRUN:
...
sprintf_overrun: "sprintf" will overrun its first argument "a.buff2" which can accommodate 8 bytes.  The number of bytes written may be 9 bytes, including the terminating null.

Компиляция со всеми включенными предупреждениями - хорошее начало:

chqrlie@mac ~/dev/stackru > clang -O3 -std=c11 -Weverything -lm -o 35996676 35996676.c
35996676.c:9:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
main(int argc, char *args[])
^
35996676.c:12:19: warning: implicit conversion changes signedness: 'unsigned int' to 'int' [-Wsign-conversi
    a.ii = a.jj = 0xdeadbeef;
                ~ ^~~~~~~~~~
35996676.c:15:5: warning: implicitly declaring library function 'sprintf' with type 'int (char *, const cha
    sprintf(a.buff2, "ABCDEFGH");
    ^
35996676.c:15:5: note: include the header <stdio.h> or explicitly provide a declaration for 'sprintf'
35996676.c:9:10: warning: unused parameter 'argc' [-Wunused-parameter]
main(int argc, char *args[])
         ^
35996676.c:9:22: warning: unused parameter 'args' [-Wunused-parameter]
main(int argc, char *args[])
                     ^
35996676.c:7:3: warning: no previous extern declaration for non-static variable 'a' [-Wmissing-variable-dec
} a;
  ^
6 warnings generated.

Исправление с помощью простых исправлений и добавление дополнительных очевидных ошибок:

#include <stdio.h>
#include <string.h>

static struct A {
    char buff1[8];
    unsigned int jj;
    char buff2[8];
    unsigned int ii;
    char buff3[8];
} a;

int main(void) {
    // Set intermediate fields to known flag value
    a.ii = a.jj = 0xdeadbeef;

    // Write 8 char string into 8 byte buffer - null will overflow into neighboring int field. ERROR
    sprintf(a.buff1, "ABCDEFGH");
    strcpy(a.buff2, "ABCDEFGH");
    sprintf(a.buff3, "%s", "ABCDEFGH");

    return 0;
}

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

gcc с -Wall -W -Wextra тоже не видит ничего плохого.

Рекомендуется использовать snprintf вместо sprintf, но это не помешало бы strcpy проблема. strcpy в общем случае небезопасно, но при наличии строкового литерала в качестве источника компилятор должен определенно жаловаться.

Вам следует попробовать Frama-c, мощную среду анализа исходного кода, доступную в открытом исходном коде по http://frama-c.com/

По моему опыту, Purify может делать то, что вы хотите. Это отрывок из старого руководства пользователя Purify, опубликованного в Интернете:

Как Purify проверяет статически выделенную память

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

Вот пример данных, которые обрабатываются функцией статической проверки:

int array[10];
main() {
    array[11] = 1;
}

В этом примере Purify сообщает об ошибке ABW при назначении array[11] потому что это 4 байта за конец массива.

Другие вопросы по тегам