Безопасный компилятор

Проблематика неопределённого поведения и его обработки компиляторами

Современные компиляторы выполняют десятки и даже сотни оптимизирующих преобразований исходной программы до генерации ассемблерного кода. Эти преобразования опираются на математическую модель — абстрактную машину языка Си (C++). Они корректно работают для полностью корректных программ, однако наличие даже единичной конструкции с неопределённым поведением (UB) аннулирует гарантии корректности для всей программы.

На практике эффекты от неопределённого поведения могут распространяться межпроцедурно и межмодульно — то есть по всей программе. Результирующий код ведёт себя непредсказуемо, при этом пользователи компилятора не получают предупреждений и не узнают, что программа некорректна.

Неожиданное поведение скомпилированной программы может проявиться только при нетипичных входных данных, обновлении компилятора, изменении параметров компиляции или переходе на другую процессорную архитектуру.

Связь с уязвимостями

На почве UB возникают дефекты безопасности. Компиляторы незаметно нарушают ожидания программистов, сформированные на основе интуитивного знания языка и фактического поведения доступного им компилятора и результирующей программы. В результате не выполняются функции безопасности, образуются уязвимости.

Безопасный компилятор C/C++

Безопасный компилятор (БК) сокращает непредсказуемые эффекты распространения UB. Он убирает либо делает явным неожиданное для программистов поведение.

Таким образом, безопасный компилятор препятствует появлению уязвимостей, вносимых в бинарный код обычными компиляторами. Это особенно важно для проектов с open source или сторонними компонентами, где риски эксплуатации выше.

Выявление и исправление ошибок — неотъемлемая часть жизненного цикла ПО, но этот процесс — длительный. Безопасный компилятор автоматически снижает риски эксплуатации zero-day уязвимостей в вашем ПО. Даже если в коде продукта или сторонних библиотеках остаются невыявленные ошибки, их эксплуатация затрудняется.

Каким уязвимостям помогает противостоять безопасный компилятор

Безопасный компилятор снижает вероятность того, что дефекты в программном обеспечении приведут к уязвимостям. Он ограничивает появление в скомпилированной программе дефектов безопасности, обусловленных следующими причинами.

Использование в продукте сторонних компонентов, в частности, open source, открывает дополнительные возможности для анализа кода с целью создания эксплойтов. Создание эксплойтов для распространённых компонентов привлекательно для злоумышленников, так как позволяет атаковать широкий круг потенциальных целей. Такой вектор атаки также усложняет оперативное применение патчей, так как требует координации между множеством вендоров и интеграторов уязвимого компонента.

БК снижает обозначенные риски, блокируя эксплуатацию уязвимостей на уровне сборки, даже если исходный код содержит ошибки. Это достигается благодаря сокращению непредсказуемых эффектов от распространения UB и форсированию расширенного набора защитных опций.

Кроме этого, БК предоставляет инструменты для обнаружения указанных дефектов на ранних этапах, дополняющие другие инструменты безопасной разработки.

Ключевые преимущества БК

Факты и примеры: как БК защищает от реальных уязвимостей

В этом разделе представлено несколько примеров уязвимостей из реестра 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, которым помогает противостоять БК третьего класса

Типовой сценарий защиты

Компания использует open source компонент на C/C++. В этом компоненте обнаруживается zero-day уязвимость, связанная с UB или небезопасной оптимизацией компилятора. Если продукт собран с помощью БК и соответствующих опций, уязвимость не эксплуатируется, и продукт защищён до выхода официального патча.

БК в конвейере разработки безопасного ПО

Связь со статическим анализом

Компилятор не только формирует итоговый бинарный код, но и анализирует исходный код программы. Безопасный компилятор выдаёт предупреждения о заведомо опасных конструкциях и останавливает процесс сборки, если был выбран соответствующий режим работы.

Чтобы не затруднять внедрение компилятора в существующий процесс разработки, количество ложноположительных срабатываний этих предупреждений сведено к минимуму. При этом пропуски срабатываний (когда ошибка не обнаруживается) — допускаются. Поэтому для более полного выявления ошибок в исходном коде рекомендуется использование профильного инструмента — статического анализатора.

Статический анализатор ищет известные паттерны ошибок в исходном коде, но не может гарантировать отсутствие уязвимостей, связанных с поведением компилятора или его оптимизаций.

Безопасный компилятор устраняет причины этих уязвимостей на этапе компиляции.

В РБПО имеет смысл использовать и статический анализатор, и безопасный компилятор. Роль БК в этой связке в гарантированной защите на уровне сборки. Эффект от применения БК моментальный, времени на разметку предупреждений не требуется. В то же время, БК не является заменой статическому анализу, т.к. он не предназначен для защиты от всех классов ошибок, присутствующих на уровне исходного кода.

Связь с фаззингом

Безопасный компилятор дополняет и динамические методы анализа кода. А именно, фаззинг-тестирование. Фаззер помогает покрыть пути выполнения, не достижимые на обычных тестах. Санитайзеры делают ошибки видимыми — вызывают аварийное завершение программы во время выполнения при реализации UB. Это позволяет заблаговременно детектировать ошибки, которые воспроизводятся только при наступлении определённых условий или на специфических архитектурах (ARM, MIPS и др.).

Получить безопасный компилятор и начать пилотирование

Мы поддерживаем два варианта безопасного компилятора, которые могут быть использованы в качестве замены открытых промышленных компиляторов GCC и Clang (LLVM):

Чтобы запросить безопасный компилятор или задать вопрос, перейдите на страницу Контакты.

Пересоберите проект с нашим компилятором и защитите свой продукт!