6.4. Рандомизация стека 1 315
0 9 a l ready_wri t ten %= O x l O O O O ;
10
1 1 width_field = { w r i te_word- a l ready_w r i t ten ) % O x l O O O O ;
1 2 if ( width_field < 1 0 ) width_field += Oxl O O O O ;
1 3 sprint f ( conver t_spec , 11 % % %du % % n " , width_fiel d ) ;
1 4 s t rcat ( format_s t r , conve rt_spec ) ;
15
1 6 / / Последнее слово
17 a l ready_wri tten += width field;
18 write word = Ох0 8 0 4 ;
1 9 a l ready_wri t ten %= O x l O O O O ;
20
2 1 width__field = ( w r i t e_wo rd- a l ready_w r i t ten ) % Ox l O O O O ;
2 2 i f ( w i dth_ f i e l d < 1 0 ) width__field += Ox l O O O O ;
2 3 sprin t f ( conve r t_spe c , 11 % % % du % % n " , width_field) ;
2 4 s t rcat ( format_s t r , conve rt_spec ) ;
Непосредственный доступ к аргументу
POSIX [ 1 1 5 ] разрешает применение преобразования к п-му после формата аргументу
в списке аргументов, а не к очередному неиспользованному аргументу. 1 1 В данном слу
чае символ спецификатора преобразования % заменяется последовательностью %n$, где n
представляет собой десятичное число в диапазоне [ 1 , { NL_ARGМAX } ] , которое указывает
позицию аргумента.
Формат может содержать как нумерованные (например, %n$ и *m$) , так и ненумерован
ные (например, % и * ) спецификации преобразования аргументов, но не оба одновременно
(исключением является одновременное использование % % и % n $ ) . Одновременное исполь
зованное в строке формата нумерованных и ненумерованных спецификаций аргументов
имеет неопределенные результаты. При использовании нумерованных спецификаций ар
гументов указание n-ro аргумента требует, чтобы в строке формата были указаны все пре
дыдущие аргументы, от первого до п - 1 -го.
В строке формата, содержащей спецификацию преобразования вида %n$, обращаться
к нумерованным аргументам в списке аргументов из строки формата можно любое коли
чество раз.
В примере 6. 1 1 показано, как спецификация преобразования вида %n$ может использо
ваться для эксплойта строки формата. Строка формата в строке 4 выглядит сложной, пока
не разобрана по частям. Первая спецификация преобразования, % 4 $ 5u, берет четвертый
аргумент ( константу 5 ) и форматирует вывод как десятичное число шириной 5. Вторая
спецификация преобразования, % 3 $ n, записывает текущее значение счетчика вывода (5)
по адресу) указанному в качестве третьего аргумента ( & i) . Затем этот шаблон повторяется
дважды. В итоге вызов p r int f ( ) в строках 3-6 приводит к выводу чисел 5, 6 и 7 в столб
цах шириной по 5 символов. Вызов p rint f ( ) в строке 8 выводит значения, присвоенные
переменным i, j и k, которые представляют собой увеличивающиеся значения счетчика
вывода из предыдущего вызова функции printf ( ) .
1 1 Строки преобразования в стиле % n $ поддерживаются в Linux, но не в Visual С++. Это не удиви
тельно, поскольку стандарт С не включает непосредственный доступ к аргументам.
1316 Форматированный вывод
Пример 6.1 1 . Непосредствен ный доступ к параметру
О1 int i , j , k = О ;
02
03 printf (
0 4 " % 4 $ 5u%3$n% 5 $ 5u%2 $n% 6 $ 5u % 1 $ n\n" ,
05 &k, &j , &i, 5, 6, 7
06 ) ;
07
08 printf ( " i %d, j %d, k %d\n" , i, j , k) ;
09
1 0 Вывод :
11 5 6 7
12 i = 5, j = 10, k = 15
Номер аргумента n в спецификации преобразования % n $ должен быть целым числом
между 1 и максимальным номером аргумента, предоставленного вызову функции. Некото
рые реализации предоставляют верхнюю границу этого значения, например, в виде конс
танты NL ARGМAX. В GCC фактическое значение времени выполнения может быть получе
но с помощью вызова sysconf ( ) .
int rnax_va lue = s ys con f ( _SC_NL_ARGМAX ) ;
Некоторые системы (например, System V) имеют малую верхнюю границу, такую как 9.
Библиотека GNU С реальной границы не имеет. Максимальное значение в Red H at 9 Linux
равно 4 096.
Эксплойт, показанный в примерах 6 . 1 0 и 6. 1 1 , можно легко изменить для применения
непосредственного доступа к аргументам. Строки 1 7- 1 9 из примера 6.9 можно удалить.
Новый код для вычисления з аписанной части строки формата показан в примере 6. 12.
Изменения внесены только в строки 13 и 23 (замена спецификаций формата используе
мыми при непосредственном доступе к аргументам ) и в строку 5 (удаление спецификаций
преобразования %х изменяет количество байтов, записанных в выходной поток) .
П ример 6. 1 2. Запись памяти с использованием непосредствен ного доступа к а ргументам
01 s tatic unsigned int a l ready_wri t ten , width f i e l d ;
02 static uns igned int write_word;
03 s tatic char conve rt_spec [ 2 5 6 ] ;
04
05 already_written = 1 6 ;
06
0 7 / / Первое слово
08 write wo rd = О х 9 0 2 0 ;
0 9 a l ready_wr i t ten % = O x l O O O O ;
10
1 1 width_field = ( wr i te word- al ready_wri t ten ) % O x l O O O O ;
1 2 i f ( width_f ield < 1 0 ) width_f ie l d += O x l O O O O ;
1 3 sprint f ( conve rt_spec , " % % 4 $ % du% % 5 $n " , width_ f i e l d ) ;
1 4 s t rcat ( forrna t_s tr , conve r t_spec ) ;
15
1 6 / / Последнее слово
1 7 a l ready_wr i t ten += wi dth field;
18 write word = Ох0 8 0 4 ;
1 9 already_wr itten %= O x l O O O O ;
20
6.5. Стратегии противодействия 1 317
2 1 w i dth_f i e l d = ( wr i te_wo rd-al ready_wri t ten ) % Oxl O O O O ;
2 2 i f ( wi dth_f ield < 1 0 ) w i dth_f i e l d += O x l O O O O ;
2 3 sprint f ( convert__s pec , " % % 6 $ % du % % 7 $ n " , width_field ) ;
2 4 s t rcat ( forma t_s t r , conve r t_spe c )
• 6.5. Стратегии противодействия
Многие разработчики, узнавая об опасности спецификатора преобразования %n, спра
шивают: "А не могут ли они (разработчики библиотек ввода-вывода) попросту от него изба
виться?" Некоторые реализации, такие как Microsoft Visual Studio, по умолчанию отключают
спецификатор преобразования %n, предоставляя функцию set_printf_count_output ( )
для его включения при необходимости. К сожалению, поскольку спецификатор преобра
зования %n давно и хорошо известен, его удаление приведет к неработоспособности боль
шого количества существующего кода. Однако для предотвращения уязвимостей строки
формата можно прибегнуть к ряду стратегий противодействия.
Исключение пользовательского ввода из строк формата
Просто следуйте рекомендации "FIOЗO-C. Исключите пользовательский ввод из строк
формата" ( The CERT С Secure Coding Standard [ 1 86 ] ) .
Динамическое применение статического содержимого
Еще одно общее предложение для устранения уязвимости строк формата - запрет ис
пользования динамических строк формата. Если все строки формата статические, уязви
мость строки формата невозможна (за исключением тех случаев переполнения буфера, ког
да целевой массив символов недостаточно ограничен) . Однако такое решение невозможно,
поскольку динамические строки формата широко используются в существующем коде.
Альтернативой стратегии динамического формата является динамическое использова
ние статического содержимого. В примере 6. 1 3 показана простая программа, которая умно
жает первый аргумент на второй. Программа также принимает третий аргумент, который
указывает, как отформатировать результат при выводе. Если третий аргумент является
строкой hex, произведение отображается в шестнадцатеричном формате с использованием
спецификатора преобразования % х; в противном случае оно отображается как десятичное
число с помощью спецификатора преобразования %d.
П ример 6.1 3. Динамические строки формата
0 1 # include <stdio . h>
0 2 # i ncl ude < s t r i ng . h>
03
0 4 i n t rnai n ( i nt a rgc , char * a rgv [ ] )
05 int х, у;
О б s t a t i c cha r forma t [ 2 5 6 ] = " % d * % d = " ;
07
0 8 х = atoi ( a rgv [ l ] ) ;
0 9 у = atoi ( argv [ 2 ] ) ;
10
1 1 i f ( s t r cmp ( a rgv [ 3 ] , " hex" ) == 0 ) {
1 2 s t rcat ( fo rma t , " 0 x%x \ n " ) ;
13
14 else {
1318 Форматированный вывод
1 5 s t rcat ( fo rmat , " %d\ n " ) ;
16
1 7 p�intf ( format , х , у , х * у ) ;
18
1 9 exit ( 0 ) ;
20
Если не обращать внимание н а очевидное и явно опасное отсутствие любой проверки
входных данных, эта программа безопасна в смысле эксплойтов строки формата. Програм
мисты также должны предпочитать функцию strtol ( ) функции atoi ( ) (см. рекоменда
цию The CERT С Secure Coding Standard [ 1 86] , "INT06-C. Используйте функцию s trtol ( )
или родственные ей для преобразования строкового токена в целое число") . Хотя поль
зователи могут влиять на содержимое строки формата, им не предоставляется полный
контроль над ней. Такое динамическое использование статического содержимого является
хорошим подходом к решению проблемы динамических строк формата.
Хотя это и неправильное решение, этот пример программы можно легко переписать с
использованием статических строк формата. Это облегчит состояние аудиторов по безо
пасности, которым иначе будет требоваться определить, является ли безопасным исполь
зование динамических строк формата.
Эта контрмера не всегда является практичной, особенно при работе с программами,
которые поддерживают интернационализацию с помощью каталогов сообщений.
Ограничение количества записанных байтов
При неверном использовании функции форматированного вывода подвержены уязви
мостям строк формата и переполнениям буфера. Переполнения буфера можно предотвра
тить путем ограничения количества байтов, записываемых функциями.
Количество байтов, записываемых функциями, можно ограничить с помощью поля
точности, являющегося ч астью спецификации преобразования % s . Например, вместо
sprint f ( bu ffe r , "Wrong com.rnand : % s \ n " , u s er ) ;
попробуйте написать
sprint f (bu ffe r , "Wrong com.rnand : % . 4 9 5 s \ n " , user ) ;
Поле точности указывает максимальное количество байтов, записываемое специфика
тором преобразования % s. В данном примере статическая строка требует 1 7 байтов (вклю
чая завершающий нулевой байт) , и точность 495 гарантирует, что получающаяся строка
разместится в 5 1 2-байтном буфере.
Другой подход заключается в использовании более безопасных версий функций библи
отеки форматированного вывода, которые менее подвержены переполнениям буфера (на
пример, snprint f ( ) и vsnp r i n t f ( ) как альтернативы spr i nt f ( ) и vspri n t f ( ) ). Эти
функции указывают максимальное количество выводимых байтов, включая завершающий
нулевой байт.
Всегда важно знать, какие функции и какие версии функций используется во время вы
полнения. Например, библиотека Linux libc4. [45 ] не имеет функции snpr i nt f ( ) . Однако
дистрибутив Linux содержит библиотеку l ibbsd, в которой имеется функция snprint f ( ) ,
игнорирующая аргумент размера. Следовательно, использование s np r i nt f ( ) с ранней
библиотекой libc4 может привести к серьезным проблемам безопасности. Если вы не счи
таете это проблемой, прочтите врезку ''Сокращения программирования'>.