Если уязвимость памяти строки кода "printf (" % s ", argv [1]); описываться как переполнение стека?

Сегодня я получил короткий "тест навыков С++" от Elance.com. Один вопрос заключался в следующем:

Что такое уязвимость безопасности в следующей строке кода:

printf("%s", argv[1]);

Вариант 1: Строка формата

Вариант 2: переполнение стека < - Это было отмечено Elance как правильный ответ

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

Я искал переполнение буфера или переполнение буфера в качестве опции.

Мне инстинктивно не понравилось переполнение стека ответов, потому что за 10 секунд я мысленно использовал то, что я считаю стандартным определением "Переполнение стека" :

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

В соответствии с этим определением "Переполнение стека" переполнение буфера полностью возможно без; это переполнение стека только в том случае, если программа пытается писать вне общего распределения стека вызывающей программы (из-за переполнения буфера или в противном случае это была бы законная запись, такая как выделение памяти для стека на основе переменных).

Мой 10-секундный инстинкт сказал мне, что "переполнение буфера" является более точным описанием проблемной строки кода выше - потому что часто (по моему опыту) имеется достаточное количество нулевых символов ('\0'), набитых данными мусора в ОЗУ, чтобы избежать фактического в таких случаях, но переполнение буфера в реализации кажется разумным возможным или даже вероятным. (Но возможность того, что printf читает мусор здесь, может предположить, что argc == 1, так что не было предоставлено пользователем argv[1]; если присутствует argv[1], возможно, можно предположить, что вероятность того, что вызывающая функция не вставлена NULL. В проблеме не было указано, присутствовал ли argv[1].)

Поскольку я думал, что здесь может быть проблема переполнения буфера, даже без, я ответил Формат строки, потому что просто передав другую строку формата, например "%.8s", проблема в большинстве случаев можно избежать, так что это похоже на общий более общий и, следовательно, лучший ответ.

Мой ответ был помечен как неправильный. Правильный ответ был отмечен как "Переполнение стека" .

Теперь мне приходит в голову, что, возможно, если предположить, что argv[1] присутствует, то возможное переполнение буфера только - это переполнение стека, и в этом случае переполнение стека может быть действительно правильным ответ. Однако, даже в этом случае, не считалось бы нечетным назвать это переполнение стека? Не переполнение буфера - лучший способ описать эту проблему, даже если присутствует argv[1]? И, если argv[1] присутствует не, не так ли неверно утверждать, что проблема представляет собой переполнение стека, а не более точное переполнение буфера?

Мне хотелось бы мнение профессионалов на этом сайте: "Переполнение стека" - это правильный способ определить проблему безопасности памяти с указанной выше строкой кода? Или, скорее, "переполнение буфера" или "переполнение буфера" , очевидно, лучший способ описать проблему? Наконец, учитывая два параметра, предоставленные для ответа на вопрос (см. Выше), является ли ответ неоднозначным или "переполнение стека" (или "строка формата" ), очевидно, лучший ответ?


Тангенциальные комментарии относительно теста Elance (не относится к вопросу в этой публикации)

Ни один тест Elance " С++" не касался любых С++-специфических функций, таких как классы, шаблоны, что-либо в STL или любой аспект полиморфизма, Каждый вопрос был вопросом "вниз и вперед", прямо с вопроса.

Потому что было много (по крайней мере, 3) другие вопросы Еланки так называемых "C навыки ++ тестом", которые были неоспоримо неправильно (например, этим вопрос: учитывая sizeof(int) == sizeof(int*) и sizeof(int) == 4, то в коде int *a, *b; a=b; b++; b-a;, что b-a, с правильным ответом, указанным как 4, а не фактическим правильным ответом 1), и учитывая тот факт, что на тесте не было вопросов, связанных с С++, я связался с Elance и планируют серьезно проводить свои проблемные испытания с организацией. Однако для вопроса, обсуждаемого в этой публикации, я не уверен, что вопрос/ответы проблематичны.

Ответы

Ответ 1

Здесь нет потенциального.

Стандарт гарантирует, что argc неотрицателен, что означает, что он может быть 0. Если argc положительно, argv[0] через argv[argc-1] являются указателями на строки.

Если argc == 0, то argv[1] - это не просто нулевой указатель - он вообще не существует. В этом случае argv[1] пытается получить доступ к несуществующему элементу массива. (argv[1] эквивалентно *(argv+1), добавление указателя разрешено, но разыменование имеет поведение undefined.) Обратите внимание, что в этом случае имя программы, которое в противном случае было бы доступно через argv[0], недоступно.

Если argc==1, то argv[1] == NULL. Оценка argv[1] совершенно верна, но она дает нулевой указатель. Передача нулевого указателя на printf с опцией "%s" имеет поведение undefined. Я полагаю, вы могли бы назвать это проблемой строки формата, но реальная проблема заключается в использовании нулевого указателя, когда требуется ненулевой указатель на строку.

Если argc >= 2, то argv[1] гарантированно укажет на строку, printf("%s", argv[1]) будет просто печатать символы этой строки, вплоть до окончательного '\0' (который, как гарантируется, существует).

В этом случае по-прежнему существует потенциальная уязвимость. Цитирование N1570 7.21.6.1 пункт 15:

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

(N1570 представляет собой черновик стандарта C, С++ относится к стандарту C для частей его стандартной библиотеки.)

Это означает, что реализация может ограничить количество символов, созданных вызовом printf. На практике, вероятно, нет оснований для введения фиксированного предела; printf может просто печатать символы по одному, пока не достигнет конца строки. Но в принципе, если strlen(argv[1]) > 4095, и если текущая реализация накладывает такой предел, то поведение может быть undefined.

Тем не менее, это не то, что я бы назвал "переполнение стека", особенно потому, что стандарт С++ не использует слово "стек" (за исключением нескольких кратких ссылок на "разматывание стека" ).

Большинство этих проблем можно избежать, сначала проверив:

if (argc >= 2) {
    printf("%s", argv[1]);
}

или, если вы чувствуете себя параноиком:

if (argc >= 2 && argv[1] != NULL) {
    printf("%s", argv[1]);
}

Ответ 2

В системе Unix argv[1] может быть недопустимый доступ к памяти сам по себе (case argc==0), указатель на (argc >= 2) или NULL (argc == 1).

Проблема с printf("%s", argv[1]); заключается в использовании указателя (argv[1]) без проверки его правильности. Все, что происходит позже, является лишь вторичным следствием. Проблема заключается в том, что не удалось проверить, что argv[1] - это то, что предназначено перед его использованием. Он может подпадать под самый общий CWE20: Неправильная проверка ввода. Ошибочно назвать это переполнением буфера или переполнением стека.

Ответ 3

Стандартный ответ на С++

Что касается языка, могут быть следующие случаи:

  • argc < 2
  • argc >= 2

В первом случае printf("%s", argv[1]) - это просто поведение undefined.

Во втором случае программа корректно сформирована (от argv[0] до argv[argc-1] гарантируется, что она будет действительной ничейной:

§3.6.1/2 [basic.start.main]

В последнем виде для целей изложения первый параметр функции называется argc, а второй параметр функции называется argv, где argc - количество аргументов, переданных программе из среды, в которой выполняется программа, Если argc отличен от нуля, эти аргументы должны быть представлены в argv [0] через argv [argc-1] в качестве указателей на начальные символы многобайтовых строк с нулевым завершением (ntmbs s) (17.5.2.1.4.2), а argv [0] - указатель на начальный символ ntmbs, который представляет имя, используемое для вызова программы или "". Значение argc должно быть неотрицательным. Значение argv [argc] должно быть равно 0. [Примечание: рекомендуется добавлять дополнительные (необязательные) параметры после этого. -endnote]

(основное внимание).

Почему переполнение стека ужасно нечетно

Учитывая, что никакая другая информация не была предоставлена ​​(как компилятор или архитектура), ответ "Переполнение стека" является просто неточным. Стандарт С++ не пытается определить, что такое "стек", поэтому "переполнение стека" почти ничего не означает для стандарта С++.

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

Что действительно происходит

В случае, когда argc < 2, никто не знает, что происходит. Стандарт не гарантирует и не указывает ничего. В случае, когда argc >= 2 программа четко определена.