Почему printf с одним аргументом (без спецификаторов преобразования) устарел?



в книге, которую я читаю, написано, что printf С одним аргументом (без спецификаторов преобразования) является устаревшим. Он рекомендует заменить



printf("Hello World!");


С



puts("Hello World!");


или



printf("%s", "Hello World!");


может кто-нибудь сказать мне, почему printf("Hello World!"); - это плохо? В книге написано, что она содержит уязвимости. Что это за уязвимости?

633   10  

10 ответов:

printf("Hello World!"); ИМХО не уязвим, но подумайте об этом:

const char *str;
...
printf(str);

если str случайно указывает на строку, содержащую %s спецификаторы формата, ваша программа будет демонстрировать неопределенное поведение (в основном сбой), тогда как puts(str) просто отобразит строку как есть.

пример:

printf("%s");   //undefined behaviour (mostly crash)
puts("%s");     // displays "%s"

printf("Hello world");

отлично и не имеет уязвимости безопасности.

проблема заключается в:

printf(p);

здесь p - указатель на вход, управляемый пользователем. Он склонен к формат строк атак: пользователь может вставить спецификации преобразования, чтобы взять под контроль программы, например, %x для сброса памяти или %n для перезаписи памяти.

отметим, что puts("Hello world") не эквивалентно по поведению printf("Hello world") но чтобы printf("Hello world\n"). Компиляторы обычно достаточно умны, чтобы оптимизировать последний вызов, чтобы заменить его на puts.

далее к другим ответам,printf("Hello world! I am 50% happy today") Это простая ошибка, чтобы сделать, потенциально вызывая всевозможные неприятные проблемы с памятью (это UB!).

это просто проще, проще и надежнее "требовать" программистов, чтобы быть абсолютно ясным , когда они хотят дословную строку и больше ничего.

и что printf("%s", "Hello world! I am 50% happy today") получает вас. Это абсолютно надежно.

(Стив, конечно printf("He has %d cherries\n", ncherries) это абсолютно не одно и то же; в этом случае программист не находится в" дословном строковом "мышлении; она находится в" форматном строковом " мышлении.)

Я просто добавлю немного информации по поводу уязвимости здесь.

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

если кто-то помещает символ строки формата в ваш printf вместо обычного строка (скажем, если вы хотите распечатать программу stdin), printf возьмет все, что он может в стеке.

Он был (и до сих пор) очень используется для использования программ в изучении стеков для доступа к скрытой информации или обхода аутентификации, например.

Пример (C):

int main(int argc, char *argv[])
{
    printf(argv[argc - 1]); // takes the first argument if it exists
}

если я поставлю в качестве входных данных этой программы "%08x %08x %08x %08x %08x\n"

printf ("%08x %08x %08x %08x %08x\n"); 

это указывает printf-функции, чтобы получить пять параметров из стека и отображать их в виде 8-значный мягкий шестнадцатеричное число. Таким образом, возможный выход может выглядеть так:

40012980 080628c4 bffff7a4 00000005 08059c04

посмотреть этой для более полного объяснения и других примеров.

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

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

если вы называете printf с помощью строки литерального формата злоумышленник не может проникнуть а %n в строку формата, и вы, таким образом, в безопасности. Фактически, gcc изменит ваш вызов на printf в вызов puts, значит, есть алтари нет никакой разницы (проверьте это, запустив gcc -O3 -S).

если вы называете printf с пользовательской строкой формата, злоумышленник может потенциально подкрасться %n в строку формата, и взять под свой контроль программа. Ваш компилятор обычно предупреждает Вас, что его небезопасно, см. -Wformat-security. Есть также более продвинутые инструменты, которые гарантируют, что вызов printf безопасно даже с пользовательскими строками формата, и они могут даже проверить, что вы передаете правильное число и тип аргументов printf. Например, для Java есть склонность к ошибкам Google а то Checker Рамки.

это ошибочный совет. Да, если у вас есть строка времени выполнения для печати,

printf(str);

довольно опасно, и вы должны всегда использовать

printf("%s", str);

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

printf("Hello, world!\n");

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

довольно неприятный аспект printf Это даже на платформах, где блуждающие чтения памяти может вызвать только ограниченный (и приемлемый) вред, один из символов форматирования,%n, вызывает интерпретацию следующего аргумента как указателя на записываемое целое число и приводит к тому, что количество символов, выводимых до сих пор, сохраняется в переменной, идентифицированной таким образом. Я никогда не использовал эту функцию сам, и иногда я использую легкие методы printf-стиля, которые я написал, чтобы включить только функции, которые я фактически использую (и не включают в себя это или что-то подобное), но подача стандартных строк функций printf, полученных из ненадежных источников, может выявить уязвимости безопасности за пределами возможности чтения произвольного хранилища.

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

при нормальных обстоятельствах, предполагая, что оптимизация компилятора не используется (т. е. printf() на самом деле называет printf(), а не fputs()), Я бы ожидал printf() выполнить менее эффективно, особенно для длинных строк. Это потому что printf() должен проанализировать строку, чтобы проверить, есть ли какие-либо спецификаторы преобразования.

чтобы проверить это, я провел несколько тестов. Тестирование проводится на Ubuntu 14.04, с gcc 4.8.4. Моя машина использует процессор Intel i5. Тестируемая программа выглядит следующим образом:

#include <stdio.h>
int main() {
    int count = 10000000;
    while(count--) {
        // either
        printf("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM");
        // or
        fputs("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", stdout);
    }
    fflush(stdout);
    return 0;
}

оба скомпилированы с gcc -Wall -O0. Время измеряется с помощью time ./a.out > /dev/null. Ниже приведен результат типичного запуска (я запускал их пять раз, все результаты находятся в пределах 0,002 секунды).

на printf() вариант:

real    0m0.416s
user    0m0.384s
sys     0m0.033s

на fputs() вариант:

real    0m0.297s
user    0m0.265s
sys     0m0.032s

этот эффект усиливается если у вас есть очень длинные строки.

#include <stdio.h>
#define STR "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
#define STR2 STR STR
#define STR4 STR2 STR2
#define STR8 STR4 STR4
#define STR16 STR8 STR8
#define STR32 STR16 STR16
#define STR64 STR32 STR32
#define STR128 STR64 STR64
#define STR256 STR128 STR128
#define STR512 STR256 STR256
#define STR1024 STR512 STR512
int main() {
    int count = 10000000;
    while(count--) {
        // either
        printf(STR1024);
        // or
        fputs(STR1024, stdout);
    }
    fflush(stdout);
    return 0;
}

на printf() вариант (бегал три раза, реальный плюс/минус 1,5 с):

real    0m39.259s
user    0m34.445s
sys     0m4.839s

на fputs() вариант (выполняется три раза, реальный плюс / минус 0,2 с):

real    0m12.726s
user    0m8.152s
sys     0m4.581s

Примечание: после проверки сборки, созданной gcc, я понял, что gcc оптимизирует fputs() вызов fwrite() вызова, даже с -O0. (Тег printf() вызов остается неизменным.) Я не уверен, будет ли это аннулируйте мой тест, так как компилятор вычисляет длину строки для fwrite() во время компиляции.

printf("Hello World\n")

автоматически компилируется в

puts("Hello World")

вы можете проверить это с разборку построек исполняемый файл:

push rbp
mov rbp,rsp
mov edi,str.Helloworld!
call dword imp.puts
mov eax,0x0
pop rbp
ret

используя

char *variable;
... 
printf(variable)

приведет к проблемам безопасности, никогда не используйте printf таким образом!

Итак, ваша книга на самом деле правильная, использование printf с одной переменной устарело, но вы все равно можете использовать printf("моя строка\n"), потому что она автоматически станет puts

для gcc можно включить специальные предупреждения для проверки printf() и scanf().

в документации gcc говорится:

-Wformat входит в -Wall. Для большего контроля над некоторыми аспектами проверки формата, параметры -Wformat-y2k, -Wno-format-extra-args,-Wno-format-zero-length, -Wformat-nonliteral,-Wformat-security и -Wformat=2 несколько доступны, но не включены в -Wall.

The -Wformat, который включен в не включите несколько специальных предупреждений, которые помогут найти эти случаи:

  • -Wformat-nonliteral предупредит, если вы не передадите строку litteral в качестве спецификатора формата.
  • -Wformat-security предупредит, если вы передадите строку, которая может содержать опасную конструкцию. Это подмножество -Wformat-nonliteral.

я должен признать, что включение -Wformat-security выявлено несколько ошибок, которые мы имели в нашей кодовой базе (модуль регистрации, модуль обработки ошибок, модуль вывода xml, все имели некоторые функции, которые может делать неопределенные вещи, если они были вызваны с % символов в их параметре. Для информации, нашей кодовой базе сейчас около 20 лет, и даже если мы знали о таких проблемах, мы были очень удивлены, когда мы включили эти предупреждения, сколько из этих ошибок все еще было в кодовой базе).

Comments

    Ничего не найдено.