Программа, работающая с привилегиями отличными от привилегий ее пользователя, предполагает, что вы защитили все данные и придирчиво просматриваете все входящие данные.
Прежде всего это относится к процедурам ввода строк. В соответствии с тем, что мы только что сказали, настойчиво требуем, чтобы вы никогда не использовали gets(char *array), так как длина строки не проверяется (замечание автора: эта процедура будет запрещена компоновщиком для новых компилируемых программ). Более коварная опасность спрятана в scanf(). Строка
scanf ("%s", string)
также опасна как и gets(char *array), но это не столь очевидно. Однако функции из семейства scanf() предлагают механизм контроля над размером данных:
char buffer[256];
scanf("%255s", buffer);
Данное форматирование ограничивает количество символов, копируемых в buffer, до 255. С другой стороны, scanf() помещает ненужные ей символы назад во входящий поток, поэтому существенно повышается риск програмных ошибок, порождающих блокировки.
В C++ поток cin заменяет классические функции, используемые в Си (даже если вы все еще можете использовать их). Следующая программа заполняет буфер:
char buffer[500];
cin>>buffer;
Как вы можете видеть, никаких проверок не делается! Мы в похожей ситуации, что и с gets(char *array) при использовании Си: двери широко открыты. Метод ios::width() позволяет указать максимальное число символов для чтеня.
Чтение данных требует два шага. Первый этап состоит в получении строки при помощи fgets(char *array, int size, FILE stream), это ограничивает размер используемой области памяти. Далее, прочитанные данные форматируются при помощи, например, sscanf(). На первом этапе может делаться большее, например включение fgets(char *array, int size, FILE stream) в цикл с автоматическим выделением требуемой памяти определенного размера. Ресширение Gnu getline() может делать это за вас. Также можно включить проверку введенных символов при помощи isalnum(), isprint() и т.д. Функция strspn() позволяет проводить эффективную фильтрацию. Программа становится немного медленнее, однако части кода, чувствительные к данным, защищены от неправильных данных бронежилетом.
Непосредственный ввод данных - не единственное место для атак. Файлы данных приложения также уязвимы, однако код, написанный для их чтения, обычно более стойкий, чем код для консольного ввода, так как программист интуитивно не доверяет содержимому файла, предоставленному пользователем.
Атаки на переполнение буфера часто опираются на кое-что еще: строки окружения. Мы не должны забывать, что программист может полностью конфигурировать окружение процесса перед его запуском. Соглашение, что строка окружения должна быть типа "ИМЯ=ЗНАЧЕНИЕ", может быть использовано злонамеренным пользователем. Использование подпрограммы getenv() требует некоторой осторожности, особенно это касается длины возвращаемой строки (сколь угодно длинной) и ее содержания (где вы можете найти любой символ включая `='). Строка, возвращенная getenv() будет обработана так же, как и предоставленная fgets(char *array, int size, FILE stream), обращая внимание на ее длину и проверку одного символа за другим.
Использование подобных фильтров похоже на доступ к компьютеру: по умолчанию любой доступ запрещен! Затем, вы можете позволить некоторые вещи:
#define GOOD "abcdefghijklmnopqrstuvwxyz\
BCDEFGHIJKLMNOPQRSTUVWXYZ\
1234567890_"
char *my_getenv(char *var) {
char *data, *ptr
/* Получение данных */
data = getenv(var);
/* Фильтрация
Замечание : понятно, что символ замены должен быть
в списке дозволенных!!!
*/
for (ptr = data; *(ptr += strspn(ptr, GOOD));)
*ptr = '_';
return data;
}
Функция strspn() упрощает эту работу: она ищет первый символ, не являющийся элементом множества допустимых символов. Возвращает длину строки (начиная от 0), которая содержит только допустимые символы. Вы никогда не должны обращать логику. Не делайте проверку по отношению к символам, которые вам не нужны. Всегда делайте проверку по отношению к "хорошим" символам.