Переполнение буфера и безопасность памяти: почему программы на C уязвимы
Коротко: Переполнение буфера возникает, когда программа записывает в область памяти больше данных, чем она вмещает; соседние данные или адреса возврата могут быть перезаписаны. Современные защиты и memory-safe языки замедляют это.
Подробнее: В языках вроде C/C++ границы массивов никто автоматически не проверяет. Если код пишет за конец, он перезаписывает соседние данные: это корень многих серьёзных уязвимостей (червь Морриса 1988, Heartbleed 2014 как чрезмерное чтение). Статья объясняет принцип и защиту без инструкций по атаке.
Подробности
Принцип
Программа резервирует, например, 8 байт под имя (char name[8]) в стеке. Если она копирует больше символов без проверки (strcpy, gets), данные заходят в соседнюю память: другие переменные, сохранённый адрес возврата или указатели на функции. Так атакующий может перенаправить ход программы. Родственное: переполнение кучи, use-after-free (использование памяти после освобождения), double free, off-by-one, ошибки форматной строки, целочисленное переполнение (даёт слишком малый буфер), чтение за границами (Heartbleed).
Механизмы защиты
- Stack canary: контрольное значение перед адресом возврата, проверяется перед возвратом.
- ASLR (случайные адреса памяти), PIE.
- NX/DEP (области данных не исполняются), W^X.
- RELRO, CFI/shadow stack (целостность потока управления), Intel CET, ARM PAC/MTE.
- Предупреждения и защита компилятора (
-Wall -Wextra,-fstack-protector-strong,-D_FORTIFY_SOURCE=2). - Санитайзеры (AddressSanitizer, UBSan) и Valgrind находят ошибки при тестировании, фаззинг (AFL++, libFuzzer) их провоцирует. Ни одна защита не идеальна; атакующие комбинируют техники (ROP). Лучшая стратегия — не допускать ошибок.
Более безопасное программирование
- Безопасные функции:
fgetsвместоgets,snprintf/strlcpyвместоsprintf/strcpy, длины проверять всегда. - C++:
std::string,std::vector,std::span,.at(), умные указатели вместоnew/delete(RAII). - Проверять ввод, размеры, следить за целочисленным переполнением.
- Memory-safe языки: Rust (владение, borrow checker), Go, Java, C#, Python, Swift: границы массивов проверяются, память управляется (но блоки
unsafe/нативные расширения остаются риском). - Политика: ведомства (BSI, CISA) рекомендуют memory-safe языки для нового ПО; ядро Linux, Windows, Android внедряют Rust.
- Ревью кода, статический анализ (clang-tidy, Coverity, CodeQL).
Классификация и обучение
- Примерно 60–70 % серьёзных уязвимостей в крупных кодовых базах C/C++ (Microsoft, Chromium) были ошибками памяти.
- Устанавливайте обновления: многие атаки используют известные ошибки в старых программах.
- Обучение (легально): исследуйте собственные небольшие программы на C с ASan/Valgrind, CTF-задачи (pwn) на учебных платформах, лекционные материалы (стек/куча, ассемблер).
- Heartbleed как пример: отсутствие проверки длины → память сервера читается.
- Экзамен: назвать устройство стека, причины, меры противодействия.
- Родственное: segmentation fault, утечка памяти, стек и куча, Valgrind.
См. также: Segmentation fault, Стек и куча, Valgrind и санитайзеры, Rust: владение и заимствование