EMZETT.
Войти

Переполнение буфера и безопасность памяти: почему программы на 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: владение и заимствование