Pull to refresh

Comments 84

Злободневно, только недавно проскакивал такой баг с sudo:

$ ln -s /usr/bin/sudo %n
$ ./%n -D9

Там была строчка а-ля: printf(argv[0]).
Почему это printf — вне закона? Ведь использование константной строки формата решает все эти проблемы?
Если правильно пользоваться функцией printf(), то никаких проблем нет. Беда в том, что printf() коварна. Человек должен хорошо знать, как ей пользоваться. В ней легко допустить ошибку и распечатать в некоторых случаях неправильные данные. Самый распространенный пример — использование %x для распечатки адреса. А надо %p. Проблема вылезает на системах с другой моделью данных (например Win64). И напечатается пол адреса. Таких претензий к printf() очень много. Проще объявить его плохой функцией, чем заставить в начале программиста читать целый трактат о том, как ей пользоваться. Тем более, сейчас есть более безопасные функции.
Не соглашусь. Давайте тогда объявим вообще всё «вне закона», т.к. при помощи стандартной библиотеки, например, можно кучу дел натворить. Наоборот, надо настаивать на корректном использовании функции, на том, чтобы читалась документация, чтобы люди досконально знали, где какие проблемы могут быть. Прятать и объявлять плохим — это плохая идея.
Ну зачем из одной крайности в другую-то. Опасность функции не имеет смысла без контекста, т.е. программистов, её использующих. Потому что именно неправильное использование программистами делает её «опасной». И вот тот факт, что функция легко может быть использована средним разработчиком неверно делает её использование нежелательным. Есть непродуктивные варианты вроде хаяния программистов, есть продуктивные вроде доведения уровня профессиональности программистов и тестирования до совершенства либо использования функций, с которыми ошибиться сложнее при том же уровне программистов и том же тестировании. Ну и второй вариант как-то попроще оказывается. Вот потому и говорят, что «лучше используйте нечто иное», хотя вы вольны, конечно, сказать, «я лучше найму гениев». Не забывайте только, что не Боги горшки обжигают.

Посмотрите, кстати, на типизированный printf из Cayenne (язык с зависимыми типами).
printf не является такой функцией. Что, кстати, и было описано в статье. Есть несколько вполне определённых случаев, в которых использование printf может быть опасно. Но это не значит, что эта функция нежелательна. Кто не знает, отстрелит себе ночу даже палкой, которая стреляет раз в год.
> printf не является такой функцией.
Да я даже у опытных людей встречал printf(str) вместо printf("%s", str); Можно, конечно, ответить, что и среди опытных дураки бывают, но вот почему-то неверного использования std::cout я не встречал, хотя, наверное, можно отыскать.

> Кто не знает, отстрелит себе ночу даже палкой, которая стреляет раз в год.
Ага, и функция qsort хорошая, крутые перцы же не ошибаются никогда. И C++ std::sort никому не нужен, ну разве только для инлайна, а static check нам не нужен.
void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ));


Хорошая функция исключает неверное её использование. Вообще. Другое дело, что это почти всегда недостижимый идеал, но это не значит, что теперь надо вообще отказаться от движения в эту сторону.
Пройдите по ссылке-то. Там printf, но его _нельзя вызвать неверно_. Даже в треде упоминаются варнинги GCC. Т.е. команда GCC признаёт опасность функции и так или иначе пытается это решить, а вы — нет.
Не может в принципе существовать функции, которая исключает её неверное использование. Покажите, где команда GCC признаёт опасность функции? Опасно её опасное использование, а не функция вообще. И таки да, в qsort ничего плохого нет. Не умеете пользоваться — так изучите же, в конце-то концов, а не рассказывайте сказки-страшилки про злой printf и люто-бешеный qsort.
> Не может в принципе существовать функции, которая исключает её неверное использование
Ну и к каким последствиям может привести «кривой» вызов printf из Cayenne?
Напомню, у printf в описании «Writes to the standard output (stdout) a sequence of data formatted as the format argument specifies», и вот сишный printf не соответствует этой спецификации (так как на деле может далеко не только вывести что-то на экран), если передавать неверные аргументы. А printf из Cayenne соответствует всегда. И поэтому исключает неверное использование.
printf не предназначен для buffer-overrun, в спецификации этого нет (это считается UB), однако способен привести к таким последствиям.
Единственное, что может не так сделать Cayenne'вский printf — вывести не то, что вы хотели, а то, что написали, но это уже исключительно ваша ошибка, потому что эффект функции полностью соответствует спецификации.

> Опасно её опасное использование, а не функция вообще
Терминологический спор у нас сейчас получится. Это очевидно. Однако опасное использование printf куда как более вероятно, чем опасное использование std::cout, и именно об этом речь. Не о чёрном и белом, а об оттенках серого.

> И таки да, в qsort ничего плохого нет
Ничего плохого нет вообще ни в чём, это понятие сугубо субъективное.

> Не умеете пользоваться — так изучите же, в конце-то концов
Кто вам сказал, что я не умею пользоваться. Тем не менее люди продолжают изобретать всё новые способы статического контроля, до dependent types уже докатились и proof assistants. Небось не знают, что надо просто «изучить же».
Кстати, как вы думаете, статический контроль типов нужен? И если нужен, то зачем? Или динамики достаточно?
> Опасно её опасное использование
Это примерно как опасно опасное использование мышьяка. Вроде бы и да, конечно, но при этом все прекрасно понимают фразу «мышьяк опасен для здоровья» (причём именно как «опасно опасное использование»), и никто не трактует её как «опасно смотреть на мышьяк».
Если уж касаться функций, то «функция опасна» намекает на наличие UB и высокую вероятность допустить сложнодиагностируемую ошибку.
Вот например у какой-нить тривиальной std::less этой проблемы нет и функция неопасна.
А взять уже хотя бы std::swap, то надо, например, знать, что он использует оператор =, и потому нельзя имплементировать operator = через default std::swap, а для начала написать конкретный. И вот тут кроется опасность.
Cayenne, кстати не обязательно, есть ведь давно уже такое и на Variadic templates, по ссылке и printf безопасный есть.
Да, Variadic templates замечательны.

Небольшой оффтоп:
Всё же Variadic templates несколько другое.
Между ними разница примерно как между C++ функцией
template <class T>
std::vector<S> foo(std::vector<T> const & x, std::function<S(T)> const & f);

И Haskell функцией
map :: [a] -> (a -> b) -> [b]


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

Если вы еще не знакомы с языками с зависимыми типами (dependent types), крайне рекомендую. Они до практики пока никак не доходят, но там всё очень интересно.
да да да, лямбда исчисление, Барендрегт, Лямбда куб и многое другое:)
… я имел ввиду, что уже есть возможность написать безопасный printf на C++.
Согласен с комментом выше. C++ довольно низкоуровневый язык (смотрю не на высшие уровни абстракции, а на низшие, на прослойку от ассемблера и железа) и позволяет (вкупе со стандартной библиотекой) при желании или незнании очень много дров наворотить. То, что прежде чем пользоваться безопасно C++ нужно прочитать целый трактат об его использовании, это же не повод отказываться от него вообще?
Для константной есть puts().
Обычно что-то становится «вне закона» после того, как оказывается, что слишком мало людей готово разбираться, как этим правильно пользоваться. Тогда вместо того, чтобы быть внимательнее, это объявляют вне закона и начинают неправильно пользоваться чем-то попроще.

С константной строкой не намного лучше. Например, в Visual C++ runtime есть _vsnwprintf_s() — недавно нашли случай, когда в нее в качестве строки форматирования подавалась строка с именем файла, содержащим многочисленные символы "%", функция просила под результат форматирования больше памяти, чем можно было выделить в программе (типа пару гигабайт), вызывающий код выдавал ошибку «недостаточно памяти».
хм, ну ведь ничего опасного не случилось? Функция не смогла выделить память и выдала ошибку, все правильно.
Что значит «опасного»? Никто не умер, нет, и деньги не пропали. Но по сути программа сломалась на ровном месте. Вызывающий код хотел всего лишь вывести в отладчик «открываю файл такой-то» и тут — тыдыщь — недостаточно памяти.
Какой еще тыдыщь? Функция вернула код ошибки и исполнение пошло дальше. По крайней мере я понял именно так.
Нет, функция вернула код ошибки, вызвавший ее код бросил исключение, стек свернулся, пользователь увидел сообщение об ошибке.
Классический DoS можно сказать. Представьте, что программа управляет космическим кораблём, летящим к Марсу является сервером, который принимает от клиентов строки. Один из них передаёт такую строку и сервер падает с Out of memory и остальным клиентам остаётся только ждать пока его поднимут админы.
Сервер не обязан «совсем падать» — он может выдать сообщение об ошибке только тому, кто направил запрос. Более вероятный сценарий такой: вы переходите в браузере по длинной сложной ссылке с кучей параметров, а сервер вам возвращает сообщение об ошибке самого общего вида «что-то пошло не так, попробуйте открыть заглавную страницу», потому что именно эта длинная сложная ссылка приводит к ситуации вроде описанной выше. Никто не умер, нет, но в итоге «не работает» на ровном месте.
Это практический идеальный случай. На практике: если не обязан — значит может; если может — значит упадёт, причём в самый неподходящий момент. Бутерброд тоже не обязан падать маслом вниз. И даже не падает когда их много, а вот последний или единственный…
Я не спорю, что падение может быть более жестким, чем предполагается. Я хотел сказать другое: не только такое жесткое падение является серьезной проблемой, более мягкие на первый взгляд проявления ошибок тоже могут быть серьезной проблемой. Вот скажем сервер выдает сообщение об ошибке на запрос определенного URL, а вы скажем этот URL разместили в качестве ссылки на скачивание вашего продукта — пользователи уже не смогут так легко скачать ваш продукт.
Это да, в любом случае ошибка на ровном вроде бы месте — это серьёзная ошибка. Хотя пример с урлом надуманный или перед тем как ссылку размещать сами по ней перешли?
Да, очень часто фраза «функция может быть небезопасной» не действует на мозг, пока не увидишь примеров, к чему это может привести. Спасибо за статью, подобные примеры стимулируют писать грамотный код.

Ну и, по аналогии, на более «высоких» языках постоянно встречаются огрехи с вводом/выводом пользовательских данных — как то XSS и SQL-инъекции.

Хотя, имхо, глобальнее проблема лежит в тотальном нежелании проверять входные данные. Банального экранирования или запрета спецсимволов (% в частности) хватило бы для защиты.
А у меня, например, это принципиальная позиция — не проверять входные данные, если к ним не выставлено требований в предметной области. Пускай пользователи вводят что хотят в ник, если заказчик это им не запретил в ТЗ, а говорить ему что-то вроде «в нике нельзя будет использовать апостроф и угловые скобки, потому что мне проще проверить и выбросить эти символы на вводе, чем каждый раз экранировать при выводе», имхо, не тру.

Да и по моему глубокому убеждению введенные пользователем данные должны быть всегда доступны в том виде, в котором он их вводил (без фанатизма, конечно, типа запоминания клавиш типа BS или стрелок). Во-первых, заставляет не расслабляться, помня что там может быть что угодно. (Например, XSS при вводе мы фильтруем, про SQL забыли и злоумышленник через SQL-inj всё-таки внёс код XSS в базу, а мы его не экранируем, потому что на входе проверили). Во-вторых, всегда возможно появление нового формата вывода, в котором будут свои небезопасные символы — надо не забыть написать новые входные фильтры, всю базу перетрясти и т. п. ВВ-третьих, ну просто неправильно это искусственно ограничивать пользовательский ввод.
GCC умеет проверять соответствие форматной строки и фактических типов аргументов, и выдавать ворнинги (которые можно превратить в error).

То, что microsoft не осилила подобную фичу — полностью на их совести.
Вот только интересно, как это поможет в рассматриваемом случае?
Выдаст ошибку времени компиляции.

#include <stdio.h>
int main(int argc, char **argv) {
    return printf(argv[0],42);
}

gcc -Wformat -Wformat-nonliteral -Werror test.c

cc1: warnings being treated as errors
test.c: In function ‘main’:
test.c:3: warning: format not a string literal, argument types not checked
Не всегда форматную строку можно сделать литералом. Бывают случаи, когда форматную строку нужно формировать динамически.
А зачем ее формировать динамически?
Тут дело не в том, что её формат строки динамически создают. Это редко кому нужно. Просто в реальных приложениях, в программах формат задаётся не литералом, а берётся, например с помощью функции из ресурсов.
В UNIX мире для локализации используется gettext. Поиск локализованного варианта производится не по целочисленному ID, а по оригинальной строке. Выглядит это так:
printf(gettext("Hello, %s"), username);

В этом случае компилятор все равно проверяет форматную строку (реализовано через атрибут format_arg).
А затем злой пользователь выставит «export LOCALEDIR=.» и создаст свой .po файл с примерно таким содержимым (реальная уязвимость в CUPS: lppasswd, CVE-2010-0393):

msgid "lppasswd: Unable to open password file: %s\n"
msgstr "chown root:root /tmp/sh; chmod 4755 /tmp/sh; %49150u %7352$hx
%49150u \
%7353$hx %14263u %7352$hn %27249u %7353$hn %1$*14951$x %1$*14620$x
%1073741824$"


Здесь использован обход FORTIFY_SOURCE — run-time защиты от %n в glibc.

Переменная LOCALEDIR это конечно самодеятельность CUPS, но подобные бывают и в других проектах, например, PostgeSQL, LyX, Lynx, ufraw, XFree86 (SF BID 7002, BID 8682), GIMP2.
Красиво, и куда интереснее примера в статье.
Только странно что работает — AFAIK при запуске суидных бинарников, окружение очищается, специально ради таких случаев (привет, LD_PRELOAD).
В гимпе по идее такая уязвимость безвредна — он же не setuid root.
Например, чтобы напечатать число с неизвестной заранее точностью:
void print(double x,int prec){
char format[20];
sprintf(format,"%%.%dlf",prec);
printf(format,x);
}
Это можно сделать проще.
printf("%.*lf", 42, 1.0);
Действительно. Почему-то именно этих строчек в документации я не заметил, хотя специально их искал.
Были еще какие-то примеры применения, но пока вспомнить их не удается.
А толку то от таких предупреждений? Проще тогда вообще printf() не использовать. Вариант printf("%d", x) достаточен только для лабораторных работ.

Я не против диагностики. Просто часто говорят, вот мол в XX есть такая диагностика, а в YY нет. Только скромно умалчивают, как много ложных срабатываний она даёт и пользуются ли ей.
К сожалению, не могу похвастаться столь обширным опытом анализа кода в разнообразных проектах, как у вас. Поэтому оценить полезность фичи «проверка форматных строк в printf» в масштабах всей индустрии я не берусь. Лично для себя, я нахожу такую возможность полезной.
Полезно. Анализатор PVS-Studio тоже по возможности старается проверять формат строк. Но если начинать реализовывать диагностики в духе «format not a string literal, argument types not checked», то скоро анализ скатится до уровня унылого г. Можно вообще на каждую сточку в Си/Си++ программе ругаться. Так точно ничего не пропустишь. :)
«Можно вообще на каждую сточку в Си/Си++ программе ругаться.»
На каждую строчку с printf, вы же ее вне закона объявили)

Но если у меня много уже написанного кода, я наверно не обрадуюсь рекомендации переписать все без printf. Безусловно, я предпочту найти все дыры с помощью статического анализатора, и закрыть их. И для этой задачи диагностика «format not a string literal, argument types not checked» критически важна, потому что позволяет реализовать абсолютно безопасный printf с минимальным рефакторингом существующего кода. Как то так:
const char *dyn_fmt_str = ...;
printf(SAFE_FMT("%d", dyn_fmt_str), 42);

То есть в каждом месте, где используется динамическая форматная строка, писать рядом эквивалентную статическую строку-литерал. При компиляции проверять соответствие статической форматной строки и типов аргументов (a-la gettext). В рантайме проверять соответствие статической и динамической форматных строк.

Мне эта фича доступна на стоковом GCC. А вашим клиентам — увы нет.

Сколько на свете программ на Си, и в скольких из них используется printf? Поверить не могу, что вы упускаете такой огромный рынок со своим нежеланием реализовать диагностику «format not a string literal, argument types not checked»!
Вы зря про нежелание реализовать диагностику. Диагностику как раз реализовать хотелось. Но мы уже имеем опыт, чтобы что-то не реализовывать.

Стороннему человеку кажется, что следовать таким рекомендациям полезно. На самом деле это не так. Шум от ложных срабатываний невероятно быстро разрастается, что делает инструмент бесполезным. Если человек встретил 100 ложных срабатываний подряд, то он или прекращает пользоваться инструментом, или не замечает ошибки. Получается, что используя инструмент, который выдает мало предупреждений, но по делу, получится исправить больше ошибок, чем если бы он был более щедр на сообщения. Отчасти, это всё пересекается с проблемой выбора. Большое количество не всегда дает лучший результат.

Я внимательно изучил вопрос касающийся printf. Разобрался чем он так опасен. И пришел к выводу, что если реализовать его в PVS-Studio, то будет полная хрень. Поэтому вместо этого я просто написал общеполезную статью. Диагностику я сделаю потом только очень простую. Если printf(xxx); используется только с одним аргументом. Все остальное делать бессмысленно.

Ели же кому то все таки очень хочется — то мы готовы сделать диагностическое правило под заказ. У нас такие уже есть. Пишите — обсудим.
«Получается, что используя инструмент, который выдает мало предупреждений, но по делу, получится исправить больше ошибок, чем если бы он был более щедр на сообщения.»

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

У вас поддерживаются аннотации на уровне исходного кода (a-la атрибуты gcc) для отключения ложных срабатываний или для того чтобы, анализатор лучше понимал семантику определенной функции?
Рад, что меня поняли. Именно универсальный. Наше преимущество — можно просто скачать, установить, проверить и найти ошибки. Ни надо писать никакие аннотации. Это очень круто.

Про отключение ложных срабатываний: www.viva64.com/ru/d/0021/

Аннотаций для лучшего понимания кода у нас нет. Быть может появится, но мы стараемся пока этого не делать. С аннотациями проще и точнее, но люди не хотят их писать. Да и не будут, если у них уже есть проект на несколько миллионов строк кода.
>Можно вообще на каждую сточку в Си/Си++ программе ругаться. Так точно ничего не пропустишь.

Я знаю, что вы иронизируете, но сейчас вы озвучили распространенное заблуждение. Почему-то считается, что нужно выдавать как можно больше предупреждений, от этого анализ как будто становится полнее и полезнее. На самом деле, конечно, нужно выдавать как можно больше полезных предупреждений, иначе пользователь просто перестанет реагировать на шум и будет глушить предупреждения, «чтобы не мешали».
Да, это печально. Люди часто пишут пожелания. Больше половины приходится отбраковывать. Очень хорошее пример получился с виртуальными функциями (ну Вы в курсе :). Человек пишет «хочу, чтобы находились классы, где нет виртуального деструктора». Формально — отличное пожелание. На практике — глупость. Причем уже реализованная в Visual C++ и потом выключенная по умолчанию, так как потом эти самые пользователи жалуются и плюются. И нам приходится быть гораздо изощреннее в диагностике. Вот только это уже совсем не «класс где нет виртуального деструктора». Но вот поди — скажи человеку, что то о чем он говорит — ему не нужно. Все такие уверенные в своих желаниях и рекомендациях. :)
Возможно это предупреждение выключено по умолчанию чтобы не было ложных срабатываний в коде на COM, где такое предупреждение будет вываливаться для каждого интерфейса.
Вы правы. Но не только там.
Это хороший пример, что кого ни спроси — все хотят такую диагностику. А как доходит до практики… :)
Я пробовал аналогичную диагностику в Clang на одном большом проекте. Результат — 0 ложных срабатываний, поймано 3 бага типа ужас-ужас. Наверное, дело в том, что там COM не используется ;)
Кстати можно указывать номер аргумента (чтобы не писать кучу %f): %13$n или %13$x
У меня вопрос крайне тупой. Разве printf кроме как в учебных прогах для взаимодействия с юзверем используют еще где то?
Да практически в любой программе, написанной на Си.
(for i in /usr/bin/*; do if (nm $i| grep -q printf); then echo $i; fi; done;) | wc -l

Результат: 703. (программы в /usr/bin используют printf)
sprintf так уж точно часто используется
Интересно, а когда будет статья об опасном операторе "/"? C его помощью можно разделить на ноль.

Всему свое место. Если разработчик С/C++ достаточно опытен и квалифицированен, то для него применение printf будет вполне безопасным и быстрым. А если нет опыта, то накосячить можно и с предлагаемой std::string.
Да, накосячить можно. Но Вы зря переоцениваете опыт. Забыться и написать глупость может и опытный программист. Но сделать эту глупость с std::string сложнее, чем оперируя Си-шными строковыми функциями.
С std::string, кстати, очень частый косяк — это запомнить указатель, который вернул c_str() и использовать его через полгода, когда строка 100 раз поменялась или вообще удалилась. Такое наверное и не задетектишь статическим анализатором, или я ошибаюсь?
Мы пока не умеем и возможно не научимся. Это почти вирутальное выполнение программы нужно.
Здесь хорошо проявит себя динамические анализаторы.
Без опыта оптимизации кто-то может в цикле занятся массовой «безопасной» конкатенацией константных строк std::string и какого вычисляемого значения. В С-строке я бы просто изменял символы в самой строке без «безопасного» создания новых объектов. И это было бы гораздо быстрее. Хотя не исключаю что опаснее.

Может просто вводить аттестацию на право использовать те или иные возможности языка и библиотек? Пилотом самолета тоже не сразу становятся.
Может просто вводить аттестацию на право использовать те или иные возможности языка и библиотек?

Хорошо бы. Да только программировать тогда будет некому. :)
Все будут принимать экзамены? :)

По мне так лучше меньше, но качественнее, чем потом выискивать такие timebomb.
>Может просто вводить аттестацию на право использовать те или иные возможности языка и библиотек?

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

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

Это я к тому, что формальная аттестация очень далека от идеала.
В каком-то виде аттестацию проходят при приеме на работу. Но в дальнейшем редко кто проверяет насколько изменились акценты и внимание разработчика. Может он стал хорошо документировать код, отлично знает какие-то специфичные библиотеки типа ACE/TAO, но от разработки с использованием стандартной библиотеки или распределения памяти его лучше освободить.

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

Еще я слышал, что в наших вертолетах винт вращается по часовой стрелке, а в импортных против. Здесь, наверное, уже так просто не поменяешь. Печаль.
UFO just landed and posted this here
Согласен, гораздо веселее проверить свой код и найти в нем что-нибудь в этом духе.
UFO just landed and posted this here
Я уже перестал что-либо понимать, но если в коде заменить
printf(name);
на
printf("%s", name);
Спасёт ли это гиганта мысли и отца русской демократии?
Да, спасет. Проблема и растет из того, что в качестве строки форматирования передается строка с неизвестным наперед текстом.
А то, что содержится в самой печатаемой строке — при выводе не надо экранировать?
Ведь тогда ANSI бомбу можно написать.
А это уже от задачи зависит.
Не, ну если человек не понимает, что он пишет, зачем его вообще пускать к программированию, да ещё и на Си? Можно с любой библиотекой такой пример написать: запихиваем куда-нибудь в память какое-нибудь значение, ба-бах, библиотека сломалась и делает полную дурь. При чём тут именно printf? Вот, например, в Вашей программе вообще есть более опасный баг с использование strcpy :) который вообще любой код позволит выполнить.

Это я к тому, что либо человек умеет писать на Си, либо не умеет. printf же — прекрасный инструмент для форматированной печати числовых и си-строковых данных. По сравнению с cin/cout — гораздо удобнее. И даже если в нём есть потенциальные уязвимости, никто его не забросит, потому что это только в студенческих проектах, надо выводить 2-3 значения за всю программу.
Про strcpy, да. Код:
char name[MAX_NAME_LEN + 1];
strcpy(name, raw_name);

стоило бы заменить на:
char name[MAX_NAME_LEN + 1];
strncpy(name, raw_name, MAX_NAME_LEN);

Несмотря на то что проверка осуществляется снаружи, это не делает вызов функции безопасным.
Лучше его заменить на:
char name[MAX_NAME_LEN + 1];
strncpy(name, raw_name, MAX_NAME_LEN);
name[MAX_NAME_LEN]=0;
Я уже много раз встречал ситуацию, что последний '\0' в strncpy теряется.
И в конце строки появляется мусор, до первого мусорного нуля.
Для win советуют использовать strsafe — StringCchCopy, например.
Я знаю, что код плох. Однако не вижу смысла это обсуждать, о чём просил в начале статьи. Его много как можно улучшить.

Интереснее другое. Как я понимаю, Вы продемонстрировали, как опасно работать со строковыми функциями. Вы улучшили код? Ничего подобное. Он такое же Г, как и мой. :-)

Здесь возможна другая ошибка — отсутствие терминального нуля. Читать для просветления: posts/ strncpy? just say no.
Это стоило бы заменить на:

std::string name(raw_name);

Точка.
очень синтетические примеры.

далеко не последние версии gcc упорно варнингами сыплют, что printf без формата вывода вызывается.
со второй проблемой уже все наелись, во многих книжках и статьях атака %n детально расписана.

ИМХО, если человек бездумно пишет код на плюсах, он и до использования printf забажет все нафиг.

первая статья была интереснее.
Знали, знали. А ещё знали, что можно передавать в качестве параметра ширину ввода-вывода printf("%0*i",5,6);

И что? Статья о том, чтобы не кормить в качестве спецификации форматирования IO-строки? Следующая статья будет про sprintf, видимо. Или strcpy.
Sign up to leave a comment.