Защитное программирование — это форма защитного проектирования, предназначенная для разработки программ, способных обнаруживать потенциальные аномалии безопасности и выполнять предопределенные ответы. [1] Оно обеспечивает непрерывное функционирование части программного обеспечения в непредвиденных обстоятельствах. Методы защитного программирования часто используются там, где требуется высокая доступность , безопасность или защищенность .
Защитное программирование — это подход к улучшению программного обеспечения и исходного кода с точки зрения:
Однако чрезмерно защитное программирование может защитить от ошибок, которые никогда не возникнут, что приведет к увеличению затрат на выполнение и обслуживание.
Безопасное программирование — это подмножество защитного программирования, связанное с компьютерной безопасностью . Безопасность — это забота, а не обязательно безопасность или доступность ( программному обеспечению может быть разрешено давать сбои определенным образом). Как и во всех видах защитного программирования, предотвращение ошибок является основной целью; однако мотивация заключается не столько в снижении вероятности сбоя при нормальной работе (как если бы безопасность была заботой), сколько в уменьшении поверхности атаки — программист должен предполагать, что программное обеспечение может активно использоваться не по назначению для выявления ошибок, и что ошибки могут быть использованы злонамеренно.
int risky_programming ( char * input ) { char str [ 1000 ]; // ... strcpy ( str , input ); // Копируем ввод. // ... }
Функция приведет к неопределенному поведению, если ввод превышает 1000 символов. Некоторые программисты могут не считать это проблемой, предполагая, что ни один пользователь не будет вводить такой длинный ввод. Этот конкретный баг демонстрирует уязвимость, которая позволяет эксплойтить переполнение буфера . Вот решение этого примера:
int secure_programming ( char * input ) { char str [ 1000 + 1 ]; // Еще один для нулевого символа. // ... // Копируем ввод, не превышая длину назначения. strncpy ( str , input , sizeof ( str )); // Если strlen(input) >= sizeof(str), то strncpy не будет завершаться нулем. // Мы противодействуем этому, всегда устанавливая последний символ в буфере в NUL, // фактически обрезая строку до максимальной длины, которую мы можем обработать. // Можно также решить явно прервать программу, если strlen(input) // слишком длинный. str [ sizeof ( str ) - 1 ] = '\0' ; // ... }
Наступательное программирование — это категория защитного программирования, с дополнительным акцентом на том, что определенные ошибки не должны обрабатываться защитным способом . В этой практике должны обрабатываться только ошибки, находящиеся вне контроля программы (например, пользовательский ввод); само программное обеспечение, а также данные из линии защиты программы должны быть доверенными в этой методологии .
const char * trafficlight_colorname ( enum traffic_light_color c ) { switch ( c ) { case TRAFFICLIGHT_RED : return "red" ; case TRAFFICLIGHT_YELLOW : return "yellow" ; case TRAFFICLIGHT_GREEN : return "green" ; } return "black" ; // Будет обработан как неработающий светофор. }
const char * trafficlight_colorname ( enum traffic_light_color c ) { switch ( c ) { case TRAFFICLIGHT_RED : return "red" ; case TRAFFICLIGHT_YELLOW : return "yellow" ; case TRAFFICLIGHT_GREEN : return "green" ; } assert ( 0 ); // Утверждаем, что этот раздел недоступен. }
if ( is_legacy_compatible ( user_config )) { // Стратегия: не доверяйте тому, что новый код ведет себя так же, как old_code ( user_config ); } else { // Резервный вариант: не доверяйте тому, что новый код обрабатывает те же случаи if ( new_code ( user_config ) != OK ) { old_code ( user_config ); } }
// Ожидаем, что новый код не содержит новых ошибок if ( new_code ( user_config ) != OK ) { // Громко сообщить и резко завершить программу, чтобы привлечь должное внимание report_error ( "Что-то пошло очень неправильно" ); exit ( -1 ); }
Вот некоторые методы защитного программирования:
Если существующий код протестирован и заведомо работоспособен, его повторное использование может снизить вероятность появления ошибок.
Однако повторное использование кода не всегда является хорошей практикой. Повторное использование существующего кода, особенно при его широком распространении, может позволить создавать эксплойты, нацеленные на более широкую аудиторию, чем это было бы возможно в противном случае, и несет с собой всю безопасность и уязвимости повторно используемого кода.
При рассмотрении возможности использования существующего исходного кода быстрый просмотр модулей (подразделов, таких как классы или функции) поможет устранить или предупредить разработчика о любых потенциальных уязвимостях и убедиться, что код подходит для использования в проекте. [ необходима цитата ]
Прежде чем повторно использовать старый исходный код, библиотеки, API, конфигурации и т. д., необходимо учесть, пригодна ли старая работа для повторного использования или она может быть подвержена проблемам, связанным с наследием .
Проблемы, связанные с устаревшими разработками, возникают, когда от старых проектов ожидают соответствия современным требованиям, особенно если старые проекты разрабатывались или тестировались без учета этих требований.
Во многих программных продуктах возникли проблемы со старым исходным кодом, например:
Известные примеры проблемы наследия:
Злонамеренные пользователи, вероятно, изобретут новые виды представления неверных данных. Например, если программа пытается отклонить доступ к файлу "/etc/ passwd ", взломщик может передать другой вариант этого имени файла, например "/etc/./passwd". Библиотеки канонизации могут быть использованы для избежания ошибок из-за неканонического ввода.
Предположим, что конструкции кода, которые кажутся проблемными (похожими на известные уязвимости и т. д.), являются ошибками и потенциальными недостатками безопасности. Основное правило: «Я не знаю обо всех типах уязвимостей безопасности . Я должен защищаться от тех, о которых я знаю , и тогда я должен быть проактивным!».
gets
никогда не следует использовать, поскольку максимальный размер входного буфера не передается в качестве аргумента. Функции библиотеки C, такие как , scanf
можно использовать безопасно, но программист должен быть осторожен с выбором строк безопасного формата, выполняя очистку перед использованием.Эти три правила безопасности данных описывают, как обращаться с любыми данными, полученными из внутренних или внешних источников:
Все данные важны, пока не доказано обратное . Это означает, что все данные должны быть проверены на предмет отсутствия ценности, прежде чем они будут уничтожены.
Все данные считаются небезопасными, пока не доказано обратное . Это означает, что все данные должны обрабатываться таким образом, чтобы не раскрывать остальную часть среды выполнения без проверки их целостности.
Любой код небезопасен, пока не доказано обратное . Хотя это название немного неверное, оно служит хорошим напоминанием о том, что никогда не следует предполагать, что наш код безопасен, поскольку ошибки или неопределенное поведение могут подвергнуть проект или систему атакам, таким как распространенные атаки с использованием SQL-инъекций .