Проблематика неопределённого поведения и его обработки компиляторами
Современные компиляторы выполняют десятки и даже сотни оптимизирующих преобразований исходной программы до генерации ассемблерного кода. Эти преобразования опираются на математическую модель — абстрактную машину языка Си (C++). Они корректно работают для полностью корректных программ, однако наличие даже единичной конструкции с неопределённым поведением (UB) аннулирует гарантии корректности для всей программы.
На практике эффекты от неопределённого поведения могут распространяться межпроцедурно и межмодульно — то есть по всей программе. Результирующий код ведёт себя непредсказуемо, при этом пользователи компилятора не получают предупреждений и не узнают, что программа некорректна.
Неожиданное поведение скомпилированной программы может проявиться только при нетипичных входных данных, обновлении компилятора, изменении параметров компиляции или переходе на другую процессорную архитектуру.
Связь с уязвимостями
На почве UB возникают дефекты безопасности. Компиляторы незаметно нарушают ожидания программистов, сформированные на основе интуитивного знания языка и фактического поведения доступного им компилятора и результирующей программы. В результате не выполняются функции безопасности, образуются уязвимости.
Безопасный компилятор C/C++
Безопасный компилятор (БК) сокращает непредсказуемые эффекты распространения UB. Он убирает либо делает явным неожиданное для программистов поведение.
Таким образом, безопасный компилятор препятствует появлению уязвимостей, вносимых в бинарный код обычными компиляторами. Это особенно важно для проектов с open source или сторонними компонентами, где риски эксплуатации выше.
Выявление и исправление ошибок — неотъемлемая часть жизненного цикла ПО, но этот процесс — длительный. Безопасный компилятор автоматически снижает риски эксплуатации zero-day уязвимостей в вашем ПО. Даже если в коде продукта или сторонних библиотеках остаются невыявленные ошибки, их эксплуатация затрудняется.
Каким уязвимостям помогает противостоять безопасный компилятор
Безопасный компилятор снижает вероятность того, что дефекты в программном обеспечении приведут к уязвимостям. Он ограничивает появление в скомпилированной программе дефектов безопасности, обусловленных следующими причинами.
- Внесение компилятором в бинарный код дополнительных дефектов на основании неявных предположений (например, об отсутствии в программе неопределённого поведения, UB).
- Игнорирование компилятором требований безопасности (например,
удаление
memsetдля очистки буферов с чувствительными данными). Это происходит потому, что требования безопасности не входят в функциональную спецификацию языка и поэтому не учитываются компилятором при оптимизации. - Явные логические ошибки программирования, такие как выход за границы массива.
Использование в продукте сторонних компонентов, в частности, open source, открывает дополнительные возможности для анализа кода с целью создания эксплойтов. Создание эксплойтов для распространённых компонентов привлекательно для злоумышленников, так как позволяет атаковать широкий круг потенциальных целей. Такой вектор атаки также усложняет оперативное применение патчей, так как требует координации между множеством вендоров и интеграторов уязвимого компонента.
БК снижает обозначенные риски, блокируя эксплуатацию уязвимостей на уровне сборки, даже если исходный код содержит ошибки. Это достигается благодаря сокращению непредсказуемых эффектов от распространения UB и форсированию расширенного набора защитных опций.
Кроме этого, БК предоставляет инструменты для обнаружения указанных дефектов на ранних этапах, дополняющие другие инструменты безопасной разработки.
Ключевые преимущества БК
- Защита от уязвимостей, вносимых компилятором (compiler-introduced security bugs, CISB). Наиболее широкий класс CISB связан с неопределённым поведением (UB) в исходной программе. БК реализует строгие правила обработки UB, устраняя целые классы дефектов.
- Стабильность и предсказуемость. Все релизы БК
проходят квалификационное тестирование, что повышает надёжность при
переходе на новые версии компилятора или переносе на новые платформы.
Это решает проблемы, с которыми сталкиваются разработчики
- при обновлении стандартных компиляторов (например, случай с Sagemath при переходе с gcc-13 на gcc-14);
- при кросс-платформенной разработке: на практике трудно обеспечить равномерное качество тестирования для всех целевых платформ, а UB часто проявляется именно в отличиях работы программы на разных платформах.
- Уникальные санитайзеры. В БК реализованы 5 дополнительных санитайзеров, которых нет в GCC и Clang. В комбинации с фаззингом или обычным тестированием они позволяют выявлять и устранять сложные ошибки, которые могут оставаться скрытыми даже при хорошем тестовом покрытии.
- Лёгкая интеграция. Для замены компилятора C/C++ на БК как правило требуется лишь задать значения параметров — путь к файлу и флаги компиляции — в системе сборки. SAFEC прошёл и продолжает проходить многолетнюю апробацию в индустрии. Выявленные случаи несовместимости с обширной кодовой базой открытого ПО и ПО компаний-партнёров анализируются и устраняются.
- Поддержка. Над безопасным компилятором работает команда специалистов ИСП РАН с многолетним стажем разработки GCC, Clang, других компиляторов и элементов тулчейна. Пользователи компилятора получают обновления, помощь по внедрению, портирование на интересующие архитектуры.
- Соответствие стандартам безопасности. БК выполняет требования к функциям безопасности компилятора для каждого из трёх классов по ГОСТ Р 71206-2024. Это важно для сертифицируемого ПО, реализующего функции безопасности (СЗИ, СКЗИ).
Факты и примеры: как БК защищает от реальных уязвимостей
В этом разделе представлено несколько примеров уязвимостей из реестра CVE (Common Vulnerabilities and Exposures), от которых защищает безопасный компилятор третьего класса. Приведены конкретные защитные механизмы, входящие в БК третьего класса.
Уязвимостям и защитным механизмам сопоставлены дефекты из перечня CWE (Common Weakness Enumeration). Это помогает понять назначение безопасного компилятора в контексте данной типологии. Он ограничивает появление этих дефектов при генерации машинного кода, либо предупреждает пользователя о возможной ошибке в исходном коде.
| Защитный механизм (входит в БК третьего класса) | Дефекты по CWE | Пример CVE | Критичность (CVSS) |
|---|---|---|---|
| Определение семантики переполнения как перехода через 0 в дополнительном коде при операциях над знаковыми целыми и указателями | CWE-190, CWE-733 | CVE-2019-1010006 | Средняя/Высокая |
| Запрет вывода о ненулевом значении указателя при разыменовании | CWE-476, CWE-733 | CVE-2009-1897 | Средняя |
| Защита от переполнения буфера на стеке | CWE-121, CWE-787 | CVE-2024-24684 | Высокая |
| Защита от переполнения при вызове функций libc | CWE-121, CWE-122, CWE-787, CWE-125, CWE-676 | CVE-2024-31570 | Высокая |
| Защита от пересечения стека с другими областями памяти | CWE-770, CWE-123 | CVE-2018-16865 | Высокая |
| Запрет оптимизаций, основанных на допустимом диапазоне значений правого операнда сдвига | CWE-1335, CWE-733 | CVE-2012-2100 | Высокая |
| Выдача предупреждений о загрузке и записи в массив за пределами памяти, выделенной для него | CWE-787, CWE-125 | CVE-2022-49218 | Высокая |
| Предотвращение замены функций работы с памятью стандартной библиотеки на эквивалентные машинные инструкции | CWE-14, CWE-733 | CVE-2023-32100 | Высокая/Средняя |
| Запрет оптимизаций на основании предположения о непересечении в памяти объектов разных типов | CWE-843 ? | ||
Запрет размещения на регистрах переменных, доступных в момент вызова
setjmp (либо выдача предупреждения) |
CWE-691 ? | ||
| Запрет оптимизации операций деления и взятия остатка в случаях, когда делитель может быть равен нулю | CWE-369, CWE-733 | ||
| Генерация позиционно-независимого кода | CWE-787 | ||
| Выдача предупреждений о делении на ноль | CWE-369 | ||
| Выдача предупреждений о некорректном использовании битовых сдвигов | CWE-1335 |
Использование защитных опций БК позволило бы предотвратить эксплуатацию или снизить критичность этих уязвимостей.
Сводный список CWE, которым помогает противостоять БК третьего класса
- CWE-14: Compiler Removal of Code to Clear Buffers
- CWE-121: Stack-based Buffer Overflow
- CWE-122: Heap-based Buffer Overflow
- CWE-123: Write-what-where Condition
- CWE-125: Out-of-bounds Read
- CWE-190: Integer Overflow or Wraparound
- CWE-369: Divide By Zero
- CWE-476: NULL Pointer Dereference
- CWE-676: Use of Potentially Dangerous Function
- CWE-733: Compiler Optimization Removal or Modification of Security-critical Code
- CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
- CWE-770: Allocation of Resources Without Limits or Throttling
- CWE-787: Out-of-bounds Write
- CWE-843: Access of Resource Using Incompatible Type ('Type Confusion')
- CWE-1335: Incorrect Bitwise Shift of Integer
Типовой сценарий защиты
Компания использует open source компонент на C/C++. В этом компоненте обнаруживается zero-day уязвимость, связанная с UB или небезопасной оптимизацией компилятора. Если продукт собран с помощью БК и соответствующих опций, уязвимость не эксплуатируется, и продукт защищён до выхода официального патча.
БК в конвейере разработки безопасного ПО
Связь со статическим анализом
Компилятор не только формирует итоговый бинарный код, но и анализирует исходный код программы. Безопасный компилятор выдаёт предупреждения о заведомо опасных конструкциях и останавливает процесс сборки, если был выбран соответствующий режим работы.
Чтобы не затруднять внедрение компилятора в существующий процесс разработки, количество ложноположительных срабатываний этих предупреждений сведено к минимуму. При этом пропуски срабатываний (когда ошибка не обнаруживается) — допускаются. Поэтому для более полного выявления ошибок в исходном коде рекомендуется использование профильного инструмента — статического анализатора.
Статический анализатор ищет известные паттерны ошибок в исходном коде, но не может гарантировать отсутствие уязвимостей, связанных с поведением компилятора или его оптимизаций.
Безопасный компилятор устраняет причины этих уязвимостей на этапе компиляции.
- Защищает от багов, которые возникают только после компиляции с определёнными флагами или на определённых версиях компилятора.
- Исключает ошибки, связанные с неопределённым поведением, которые не всегда видны статическому анализу. UB — это динамическое свойство программы.
- Обеспечивает одинаковую обработку UB на всех поддерживаемых архитектурах и версиях компилятора.
В РБПО имеет смысл использовать и статический анализатор, и безопасный компилятор. Роль БК в этой связке в гарантированной защите на уровне сборки. Эффект от применения БК моментальный, времени на разметку предупреждений не требуется. В то же время, БК не является заменой статическому анализу, т.к. он не предназначен для защиты от всех классов ошибок, присутствующих на уровне исходного кода.
Связь с фаззингом
Безопасный компилятор дополняет и динамические методы анализа кода. А именно, фаззинг-тестирование. Фаззер помогает покрыть пути выполнения, не достижимые на обычных тестах. Санитайзеры делают ошибки видимыми — вызывают аварийное завершение программы во время выполнения при реализации UB. Это позволяет заблаговременно детектировать ошибки, которые воспроизводятся только при наступлении определённых условий или на специфических архитектурах (ARM, MIPS и др.).
Получить безопасный компилятор и начать пилотирование
Мы поддерживаем два варианта безопасного компилятора, которые могут быть использованы в качестве замены открытых промышленных компиляторов GCC и Clang (LLVM):
- SAFEC (на основе GCC);
- Safelang (на основе Clang).
Чтобы запросить безопасный компилятор или задать вопрос, перейдите на страницу Контакты.
Пересоберите проект с нашим компилятором и защитите свой продукт!