Привилегированная программа должна тщательно проверять любые данные, поступающие не из доверенных источников, прежде чем предпринимать на их основе какие-либо действия. Проверка может включать в себя подтверждение того, что все числа попадают в заданный диапазон и что строки имеют приемлемую длину и состоят из разрешенных символов. Данные, требующие проверки, могут поступать в виде файлов, создаваемых пользователями, аргументов командной строки, интерактивного ввода, CGI-ввода, почтовых сообщений, переменных среды, каналов межпроцессного взаимодействия (очереди типа FIFO, разделяемая память и т. д.), доступных недоверенным пользователям, или сетевых пакетов.
Программы, устанавливающие пользовательский идентификатор, не должны делать необоснованные предположения относительно исходной среды, в которой они выполняются. Например, стандартные потоки ввода, вывода или ошибок могут быть закрыты (это могла сделать программа, которая запустила ваше приложение). В этом случае при открытии файла можно случайно задействовать дескриптор с номером 1 (к примеру) и записывать данные в обычный файл вместо стандартного вывода.
Существует множество моментов, которые нужно учитывать. Например, процесс может исчерпать различные ограничения на ресурсы, такие как количество процессов, доступных для создания, процессорное время или размер файла. В результате этого могут завершиться неудачно разные системные вызовы, и вы можете получить разные сигналы. Злоумышленники, пытаясь взломать вашу программу, могут нарочно попытаться устроить такой сценарий.
Остерегайтесь ситуаций, когда входящее значение или скопированная строка превышают размер выделенного буфера. Никогда не используйте вызов gets() и будьте осторожны при работе с функциями scanf(), sprintf(), strcpy() и strcat() (например, применяйте инструкцию if, которая предотвращает переполнение буфера).
Переполнение буфера делает программу уязвимой к
Чтобы усложнить задачу повреждения стека (в частности, чтобы сделать эту процедуру более затратной по времени, когда она проводится с удаленными сетевыми серверами), ядро Linux, начиная с версии 2.6.12, поддерживает рандомизацию адресного пространства (Address Space Layout Randomization, или ASLR). Эта технология случайным образом меняет местоположение стека в верхнем сегменте виртуальной памяти размером 8 Мбайт. Также случайные адреса могут выбираться для отображения в памяти, если ограничение RLIMIT_STACK не бесконечно, а файл /proc/sys/vm/legacy_va_layout (поддерживается только в Linux) содержит значение 0.
Более современные реализации архитектуры x86-32 предоставляют аппаратную поддержку маркировки таблиц памяти метками NX (no execute), которые предотвращают их выполнение. Эта технология не дает выполнять код программы в стеке, что делает повреждение стека еще более затруднительным.
Многие функции, упомянутые выше (например, snprintf(), strncpy() и strncat()), имеют безопасные альтернативы, позволяющие вызывающему процессу указать максимальное количество символов, которые должны быть скопированы. Они учитывают это ограничение и позволяют избежать переполнения заданного буфера. В целом эти альтернативы являются предпочтительными, но и их следует использовать осторожно. В частности, нужно учитывать следующие моменты.
• При достижении максимума большинство этих функций усекает исходную строку, помещенную в заданный буфер. Поскольку такое усечение может расходиться с семантикой программы, вызывающий процесс должен проверять, произошло ли оно (например, используя значение, возвращенное вызовом snprintf()), и при необходимости предпринимать соответствующие меры.
• Функция strncpy() может отрицательно сказаться на производительности. Например, если в вызове strncpy(s1, s2, n) длина строки, на которую указывает s2, меньше n байт, тогда недостающее место заполняется нулевыми символами, чтобы соблюсти указанную длину при записи.
• Если значения максимального размера, переданного функции strncpy(), не хватает для добавления в конце строки нулевого символа, итоговая строка