Почему инструменты статического анализа упускают этот, казалось бы, очевидный случай?

У меня есть очень простая программа на C с потенциальным переполнением буфера с использованием strcpy:

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

void buffer_overflow(char* dst, const char* src)
{
        strcpy(dst, src);
}

int main(int argc, char** argv)
{
        if(argc == 2)
        {
                char buffer[16] = {0};
                buffer_overflow(buffer, argv[1]);
                printf("[%d]: %s", (int)strlen(buffer), buffer);
        }

        return 0;
}

Ни статический анализатор clang (использующий scan-build gcc -O0 -g3 -gdwarf-2), ни cppcheck (использующий cppcheck --enable=warning,style) не считают это проблемой.

Я просто требую слишком многого от своих инструментов статического анализа?


person Chad    schedule 08.03.2019    source источник
comment
Не уверен, в чем проблема, с точки зрения статического анализа, эта программа на 100% в порядке? Проблемы вызывают динамические параметры, но как статический анализ должен предвидеть, что вы вводите? Предположим, вы сделали это правильно и сначала вызвали strlen, а затем зарезервировали память. Что, если я вызову вашу программу с аргументом длиной в два гигабайта?   -  person Damon    schedule 09.03.2019
comment
cppcheck считает strcpy() проблематичным, если я не прячу его в вопросе {{buffer_overflow()}}. Статический анализатор Clang специально имеет анализатор для strcpy API.   -  person Chad    schedule 09.03.2019
comment
@Damon: цель статического анализа - сказать вам, что есть обстоятельства, при которых ваше приложение может пойти не так. Понятно, что это может случиться с большими размерами argv[1], так что теоретически анализаторы могут обнаружить это. (В конце концов, вы можете это обнаружить, почему алгоритм не может?)   -  person Ira Baxter    schedule 09.03.2019


Ответы (2)


Я не могу говорить о качестве "ваших" инструментов статического анализа.

Вот инструмент динамического анализа CheckPointer от моей компании, который обнаруживает проблему ( s) с вашим кодом (который я тестировал как "buggy.c"):

C:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example>runexample
RunExample.cmd 1.2: Batch file to execute C CheckPointer example
Copyright (C) 2011-2016 Semantic Designs; All Rights Reserved
c:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example\Source\buggy.c
*** Instrument source code for memory access checking
Copyright (C) 2011 Semantic Designs; All Rights Reserved
C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential
Powered by DMS (R) Software Reengineering Toolkit
*** Unregistered CheckPointer Version 1.2
*** Operating with evaluation limits.
Parsing source file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
Writing target file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code
gcc.exe -I"c:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c ...


C:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example\Source>C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example\buggy.exe foo
[3]: foo

C:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example\Source>C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example\buggy.exe 0123456789ABCDE
[15]: 0123456789ABCDE

C:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example\Source>C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example\buggy.exe 0123456789ABCDEF
*** Error: CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
       Dereference of pointer is out of bounds.
  in wrapper function: strcpy
called in function: buffer_overflow, line: 6, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
called in function: main, line: 14, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
*** Error: CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
       Dereference of pointer is out of bounds.
  in wrapper function: strlen
called in function: main, line: 15, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
*** Error: CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
       Dereference of pointer is out of bounds.
  in wrapper function: printf
called in function: main, line: 15, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
[16]: 0123456789ABCDEF
C:\DMS\Domains\C\GCC4\Tools\CheckPointer\Example\Source>
person Ira Baxter    schedule 08.03.2019
comment
это из Semantic Designs? Он также поддерживает C++? - person Chad; 09.03.2019
comment
Да, это от Semantic Designs. Когда я говорю такие вещи, меня, как правило, обжигают ТАК ненавистники коммерческих продуктов. Нет, для C++ у нас нет. Все же. - person Ira Baxter; 09.03.2019

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

Проблема 1: Ложные срабатывания

Мы с вами знаем, что argv[1] может быть произвольно длинной строкой. (Отчасти это знание получено из контекста вопроса о переполнении стека, в котором код обычно представлен без какого-либо контекста или предварительных предположений.) Но если статический анализатор сообщает о каждом strcpy, где исходная строка не была известна (по статический анализатор!) иметь ограниченную длину, на практике он будет сообщать очень большое количество того, что большинство разработчиков сочло бы ложными срабатываниями (FP): неверные отчеты, которые они не хотят видеть.

Это связано с тем, что существует очень большое количество случаев, когда strcpy используется правильно, но причина, по которой это правильно, выходит за рамки разумных возможностей анализатора и может вообще не присутствовать в исходном коде (например, «программа X — это всего лишь когда-либо вызываемая программой Y, которая никогда не передает аргументы длиннее 80 символов"). Для сравнения, только очень небольшая часть вызовов strcpy (с аргументами, которые анализатор не может связать) ошибочны. Давайте будем великодушны и скажем, что 10% из них неверны — это все равно будет 90% FP, если мы сообщим их все, что намного превышает то, что большинство разработчиков потерпит.

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

Проблема 2: Межпроцедурный анализ

Даже для инструмента, который хочет сообщить об этом (как, по вашим словам, делает Cppcheck, когда strcpy находится непосредственно в main), второй проблемой является понимание того, что делает buffer_overflow. Для статического анализатора нецелесообразно просто «встраивать» содержимое вызываемых объектов в вызывающие объекты с произвольной глубиной, потому что результирующий AST будет огромным, а количество путей — астрономическим. Следовательно, анализаторы обычно обобщают поведение вызываемого объекта. Точная форма этих сводок и алгоритмы, которые их вычисляют, являются предметом активных академических исследований и строго охраняемой коммерческой тайной.

Поведение strcpy на самом деле довольно сложное по сравнению с тем, что может выразить типичная сводка функций. Здесь задействован размер, но этот размер получается путем проверки содержимого данных, на которые указывает один из указателей, в частности, местоположения первого NUL-байта. Затем этот размер влияет на то, что происходит с вещами, указываемыми обоими аргументами. Это много, чтобы закодировать сводку в общем и масштабируемом виде, поэтому большинство инструментов этого не делают. В результате инструмент имеет очень приблизительное представление о том, что делает buffer_overflow, как правило, слишком грубое, чтобы позволить ему уверенно сообщить здесь о дефекте.

person Scott McPeak    schedule 08.09.2019