Программа выдала не первый и второй символы пароля, а… первый и пятый! Поэтому, от надежды получить пароль в удобочитаемом виде приходится отказываться, возвращаясь к использованию спецификатора “%x”.
Описанная методика, строго говоря, никаким боком не относится к переполнению буфера и никак не может воздействовать на стек. Однако чтение содержимого стека способно нанести не меньший урон безопасности системы, чем традиционное переполнение буфера. О существовании уязвимости в функции printf догадываются не все программисты, поэтому-то большинство приложений, считающиеся надежными, могут быть атакованы подобным образом.
Для устранения угрозы проникновения систему некоторые разработчики пытаются фильтровать ввод пользователя. Но это плохое решение, поскольку пользователь вполне может выбрать себе пароль наподобие «Kri%s» и будет очень удивлен, если система откажется его принять. Но существует простой и элегантный выход из ситуации, который продемонстрирован в листинге, приведенном ниже: (на диске, прилагаемом к книге, он находится в файле “/SRC/buff.printf.nobug.c”):
От файла demo.printf.c он отличается всего одной строкой, которая выделена жирным шрифтом. Только самый левый аргумент функции printf может содержать в себе спецификаторы, во всех остальных случаях они будут проигнорированы. Это доказывает следующий эксперимент:
· buff.printf.nobug.exe
· printf bug demo
· Login:kpnc
· Passw:
· Invalid password:
Теперь никакая строка, введенная пользователем, не сможет вызвать непредсказуемого поведения программы! И нет никакой необходимости прибегать к фильтрации ввода, которая сама по себе чревата внесением новых ошибок! Для выявления всех уязвимых мест в программе достаточно воспользоваться шаблонным поиском.
Ошибки, приводящие к переполнению буфера, выявить сложнее. Попытка протестировать программу на строках непомерной длины не всегда дает желаемый результат. Во многих случаях ошибки проявляются только при вводе строк определенной длины. Как раз такую ситуацию и демонстрирует следующий пример (на диске, прилагаемом к книге, он находится в файле “/SRC/buff.arg.c”):
Это ошибка особенно распространена среди начинающих программистов, но порой встречается и у профессионалов. Строка длиной в десять байт не может поместиться в десятибайтовый буфер, поскольку на ее конце находится завершающий нуль! В результате один байт «вылезает» из буфера! Но все строки длиннее десяти символов отсекаются программой, и ошибка проявляется только на десяти символьных строках!
Ошибка переполнения в один байт встречается достаточно часто. К этому приводит путаница между длинами и индексами массивов, начинающихся с нуля; выполнение операции сравнения до модификации переменной; небрежное обращение с условиями выхода из цикла и т.д. Существует даже шуточное выражение «ошибка в плюс-минус один байт!», один из способов устранения которой заключается в подгонке значения «капризных» переменной уменьшением или увеличением их значения на единицу.
Например, если “if (p»strlen(str)) break” не работает, то некоторые программисты «прыгают блохой» на единицу назад “if (p»(strlen(str)-1)) break” [319]. Но если «ошибка в плюс-минус один байт» не проявит себя на тестовых прогонах программы, она имеет шанс дожить до финальной версии и вместе с ней попасть на компьютер потенциальной жертвы.