Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Как я искал уязвимости на Rust: 900 гипотез и 12 миллиардов токенов

Как я искал уязвимости на Rust: 900 гипотез и 12 миллиардов токенов

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

В результате у меня родился целый исследовательский комплекс rust-in-peace, учебный полигон, специфические сигнатуры статического анализа и реальные находки в сторонних библиотеках. В этом материале я подробно расскажу, как строился этот конвейер, с какими ложными гипотезами я столкнулся и во сколько в итоге обошлась автоматизация поиска.

Инфраструктура тестирования безопасности
Вспомогательный элемент инфраструктуры для запуска защищенных проверок кода.
Анализ защищенности компонентов
Дополнительный визуальный контекст проверки программных модулей.
Конфигурация тестовых сред
Настройка параметров изолированного окружения для тестирования.
Скрипты автоматизации проверок
Фрагменты автоматизации конвейера поиска ошибок.
Финальные отчеты аудита безопасности
Итоговая визуализация метрик проверки и генерации отчетов.

Харнесс, который смог и концепция конвейера

Созданный мной инструмент rust-in-peace объединяет анализ исходного кода с помощью агентских и традиционных подходов, а также реальную проверку поведения приложений. Несколько изолированных проходов генерируют потенциальные гипотезы об уязвимостях, после чего другие агенты стараются их опровергнуть. Для уцелевших утверждений подбираются целенаправленные эксперименты — будь то вызовы публичных API, фаззинг, отправка специфических последовательностей сетевых протоколов или инструментальный контроль.

Реклама

Эту систему я выстраивал постепенно, двигаясь от простых экспериментов к сложным многоуровневым проверкам. Чтобы понять природу возможных сбоев, важно вспомнить базовые гарантии безопасности, заложенные создателями языка.

Система rust-in-peace с независимыми проходами анализа
Три независимых прохода и дополнительный SAST формируют базу вопросов для проверки.

Что именно обещает современный компилятор

Системы владения (ownership), заимствования (borrowing) и правила времени жизни (lifetimes) позволяют компилятору эффективно отслеживать ссылки на объекты без использования сборщика мусора. Дополнительные маркеры Send и Sync регулируют безопасную передачу и совместное применение данных между потоками. Всё это кардинально сужает поле типичных ошибок, которые годами преследуют разработчиков на C и C++.

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

Прикладная разработка всегда упирается в границы компонентов. На практике изоляция данных клиентов, корректная обработка сетевых заголовков и соблюдение допустимых переходов состояний протокола целиком ложатся на плечи программиста. Ярким примером служит уязвимость CVE-2023-26964 в библиотеке h2, где память не выходила за допустимые адреса, но из-за специфической комбинации HTTP/2-кадров её расходовалось критически много.

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

Учебное приложение и бумажная безопасность

Для погружения в тему я написал Damn Vulnerable Rust Application (DVRA). В нем я намеренно заложил классические проблемы: нарушения прав доступа, SSRF, инъекции команд, каталоговые обходы при распаковке архивов, проблемы с синхронизацией и дефекты разбора входных потоков.

К проекту прилагалась четкая модель угроз, выступившая в роли свода жестких запретов. Первые запуски классических сканеров безопасности ожидаемо не принесли серьезных результатов, после чего я обратил внимание на современные агентские инструменты статического анализа. За основу был взят Defending Code Reference Harness, который я адаптировал под особенности экосистемы Rust.

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

Разветвление исследовательского процесса статического анализа SAST
Результаты обработки 305 групп сигналов статическим анализатором и последующей проверки контекста.

Очень убедительная ошибка и требования к зависимостям

Настоящий код сторонних библиотек быстро показал всю коварность автоматизированного анализа. В x509-parser агент выдал невероятно убедительный сценарий с использованием пустого RSA-значения. Логика казалась безупречной, однако при попытке собрать рабочий proof-of-concept выяснилось, что данные предварительно фильтровались в соседней зависимости asn1-rs, полностью прерывая опасную цепочку.

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

Спираль и память об ошибках

В основе методологии лежала концепция Spiral, адаптированная под проектирование детекторов уязвимостей. Каждая итерация включала четкую последовательность: гипотеза, эксперимент, проверка на опровержение, выводы и модификация инструментария.

Переход от гипотезы к фаззингу и исполнению в контейнерах
Связывание тестового стенда с реальным API и воспроизведение сбоев с помощью libFuzzer.

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

Реклама

Изоляция проходов и слепой поиск

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

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

Практическая польза статичного анализа SAST

Тестирование стандартных наборов правил для OpenGrep, CodeQL и профильных репозиториев показало специфическую картину. Большинство готовых запросов давали колоссальное число срабатываний на тестовых паролях или криптографических алгоритмах вроде RC4, сводя реальную пользу к минимуму.

Схема ошибки учета HTTP/2 потоков в библиотеке h2
Схема возникновения уязвимости h2 после PUSH_PROMISE и последующей паники в среде Deno.

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

От гипотезы к работающему стенду фаззинга

Обнаружение теоретического дефекта требует практического подтверждения. Для этого я разработал профиль автоматического подбора тестовых стендов. В зависимости от типа уязвимости система подключала AddressSanitizer, Miri либо настраивала направленный фаззинг через cargo-fuzz.

Важным архитектурным решением стала изоляция: поиск уязвимостей и их проверка исполнялись в строго разнесенных контейнерах. Это исключало случайное искажение среды тестирования.

Разбор реальных кейсов: h2 и RustDesk

В популярной библиотеке h2 была выявлена ошибка учета HTTP/2-потоков. После получения кадра PUSH_PROMISE повторная отправка информационных ответов 1xx приводила к падению процесса в приложениях вроде Deno, собиравшихся с параметром panic=abort. Проблема решилась точечной корректировкой условий в библиотеке и отключением ненужного server push на стороне клиента.

В проекте RustDesk обнаружилось повторное использование потока шифрования из-за одинаковых начальных значений nonce и общего ключа, что позволяло наблюдателю связывать открытые тексты. Выпуск новой версии обмена с отдельными подключами для каждого направления полностью устранил этот архитектурный просчет.

Аудит ядра Linux и собственные уязвимости

Исследование коснулось и подсистемы Rust Binder в ядре Linux, где массив четырехбайтовых значений приводил к избыточным запросам физически непрерывной памяти. Замена стандартных векторов на специализированный тип KVVec успешно решила проблему выделения ресурсов.

На финальном этапе я проверил собственную инфраструктуру конвейера rust-in-peace и обнаружил архитектурный изъян: агент со служебными токенами API выполнялся в том же контейнере, что и исследуемый код.

Итоговая цена автоматизированного поиска

Подводя итог масштабному эксперименту, можно констатировать колоссальные затраты ресурсов. Суммарно система обработала около 12 миллиардов токенов, задействовав более 75 тысяч обращений к языковым моделям и потребовав порядка 210 агенто-часов активной работы.

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

01.