РАЗБОР
Статья
Fuzzilli против BigSleep: кто ищет баги в V8
[ обложка статьи ]
Режим чтения
По данным Google Project Zero - 45 actionable-уязвимостей за полгода у BigSleep, 37 у Fuzzilli, пересечение 25%. Но стоит поставить BigSleep в контролируемый эксперимент, где он переоткрывает исторические баги Fuzzilli, - пересечение вырастает до 60%. Это не артефакт подсчёта. Два подхода ищут баги в разных пространствах: один перебирает программы, другой строит гипотезы о коде. Русскоязычные публикации по теме ограничились пересказом блог-поста Project Zero. Здесь - разбор методологии по доступным публикациям (конкретные цифры требуют верификации по первоисточникам), баги, которые находит только один подход, и данные по гибридам, пытающимся закрыть gap между coverage-guided fuzzing и AI-driven vulnerability research.
Почему уязвимости движка V8 - критический таргет
V8 давно перестал быть "движком Chrome". Он стоит за Node.js, Deno, Bun, Electron-приложениями (VS Code, Slack, Discord), Cloudflare Workers, AWS Lambda - от браузера до edge-compute. Одна type confusion в JIT-компиляторе - и вот у тебя RCE через вредоносную веб-страницу и компрометация миллиардов устройств.Автоматизированный поиск 0-day в V8 - не академическое упражнение, а частный случай более широкой дисциплины, которой посвящено полное руководство по бинарному анализу уязвимостей. Это вопрос operational security для всего стека, который стоит поверх движка.
Scope: почему сравниваются именно Fuzzilli и BigSleep
Обе системы работают в продакшене Google, обе таргетируют V8, обе репортят через ClusterFuzz в единый баг-трекер Chromium с общей дедупликацией. Единственная пара инструментов с шестимесячными параллельными данными на одном таргете и единой системой подсчёта.
Альтернативные фаззеры JavaScript-движков (Montage, DIE, Comfort) и альтернативные LLM-агенты (CodeMender, ранние версии Naptime) либо не имеют сопоставимого объёма production-статистики, либо работают на других таргетах. StateLens - гибрид с LLM-guided инструментацией - рассматривается отдельно: его данные получены в контролируемом 72-часовом тесте на шести JS-движках, а не в полугодовом продакшене.
Ограничения:
- Результаты справедливы для V8, Fuzzilli в конфигурации ClusterFuzz и BigSleep с Gemini (версия не подтверждена публично)
- Cost-per-bug не сравнивается - ни одна система не оптимизирована по стоимости (Project Zero явно это оговаривает)
- Экстраполяция на SpiderMonkey, JavaScriptCore или другие LLM требует отдельных экспериментов
Архитектура Fuzzilli: фаззинг V8 через промежуточное представление
Fuzzilli - coverage-guided мутационный фаззер JavaScript-движков, созданный Samuel Gross (Saelo). Принципиальное отличие от grammar-based фаззеров вроде Dharma или Grammarinator: мутации выполняются не на JavaScript-коде, а на промежуточном представлении FuzzIL. Любая FuzzIL-программа гарантированно лифтится в синтаксически и семантически валидный JS. Для фаззинга V8 и JIT-компиляторов, где баги проявляются только на корректном коде, это принципиально.FuzzIL и REPRL
FuzzIL-программа - плоский список инструкций, где каждая операция принимает входные переменные и порождает новые:
Код:
v0 <- LoadInteger '0'
v1 <- LoadInteger '10'
v3 <- LoadInteger '0'
BeginFor v0, '<', v1, '+', v2 -> v4
v6 <- BinaryOperation v3, '+', v4
Reassign v3, v6
EndFor
v9 <- LoadGlobal 'console'
v10 <- CallMethod v9, 'log', [v3]
for (let v4 = 0; v4 < 10; v4++) { v3 = v3 + v4; } console.log(v3); - валидный JavaScript. Каждая мутация имеет малую вероятность сломать семантическую корректность программы, потому что оперирует на уровне переменных и операций, а не синтаксических конструкций.Выполнение - через REPRL (Read-Eval-Print-Reset-Loop). Модифицированный движок принимает скрипты через pipe и shared memory, выполняет, сбрасывает внутреннее состояние и ждёт следующий скрипт. По измерениям Doyensec, REPRL даёт примерно семикратное ускорение по сравнению с forkserver - экономия на
fork() и реинициализации движка. Но за это приходится платить: нужна кастомная модификация JS-движка. Добавление команды fuzzilli с операторами FUZZILLI_CRASH и FUZZILLI_PRINT, настройка дескрипторов для ввода/вывода. Патчи для V8, SpiderMonkey и JavaScriptCore лежат в репозитории Fuzzilli.Coverage-guided мутации и масштабирование
Edge coverage собирается через Clang-флаг-fsanitize-coverage=trace-pc-guard. Guard'ы записываются в POSIX shared memory bitmap; после каждого прогона bitmap читается фаззером и guards сбрасываются.Четыре типа мутаций на FuzzIL:
- InputMutator - подменяет входные переменные инструкций, перестраивая dataflow программы
- CodeGenMutator - генерирует новый код или сплайсит фрагменты из корпуса
- CombineMutator - вставляет целую программу из корпуса в случайную позицию
- OperationMutator - меняет параметры операций: константы, строки, типы
Что фаззер видит хорошо - нелокальные interaction bugs. Конфликты JIT-оптимизаций, GC и hidden class transitions, где для триггера нужна конкретная комбинация операций. Такое проще нащупать мутацией, чем вывести логически.
Что фаззер не видит - coverage plateau. Когда edge coverage насыщен, баги за разными runtime-состояниями при одинаковом control flow остаются невидимыми. Логические ошибки в спецификации, не открывающие новых edge'ей, - тоже мимо.
BigSleep: LLM-агент Project Zero для поиска уязвимостей V8
BigSleep - развитие проекта Naptime (2024), LLM-агентная система для автоматизированного поиска уязвимостей в свежем коде. В отличие от Fuzzilli, BigSleep не исследует пространство программ: он читает код, рассуждает о нём и целенаправленно строит тесткейсы.
Агентный цикл: от коммита до PoC
- BigSleep получает список коммитов V8 за сутки
- Для каждого diff'а формирует гипотезу: какие инварианты могли нарушиться?
- Генерирует PoC-тесткейс на JavaScript
- Запускает PoC на pre-fix и post-fix билдах
- Крэш на pre-fix, но не на post-fix - файлит через ClusterFuzz
Контекст масштаба: по заявлениям Google (точные даты и версии не подтверждены на момент публикации), AI-driven vulnerability detection планируется распространить на всю кодовую базу Chrome. Утверждается, что за несколько milestone количество исправленных security-багов значительно превысило показатели предыдущих релизов. BigSleep в V8 - часть этого заявленного сдвига.
Что агент видит хорошо - логические баги в новом коде, где семантика diff'а указывает на проблему. Spec-level ошибки: некорректная элиминация проверок, truncation, off-by-one в граничных условиях. Агент понимает intent разработчика и проверяет edge cases, не покрытые тестами.
Что агент не видит - нелокальные взаимодействия. Если баг возникает из комбинации двух независимых оптимизаций, commit-review по отдельности видит каждый diff корректным.
Продакшен за полгода: от 441 сырого репорта до 45 уязвимостей
Данные, предположительно полученные из баг-трекера Chromium (период и цифры не подтверждены независимыми источниками):| Метрика | Fuzzilli | BigSleep |
|---|---|---|
| Сырые баг-репорты | 316 | 441 |
После фильтра type:vulnerability | 178 | 95 |
| Actionable (fix-коммит, без дублей/WAI) | 37 | 45 |
| Ratio raw -> actionable | 8.5x | 9.8x |
Переход BigSleep от 441 к 45 - показательная воронка. Значительная часть отсечённых репортов - конфигурационные проблемы (пример: crbug.com/issues/503869331 - некорректная блокировка unsafe-флагов), дубликаты и баги, закрытые как WAI (Working As Intended). Для сравнения фаззера и LLM в контексте security impact они нерелевантны.
Само определение "actionable" тоже стоит покрутить. Fuzzilli-баг crbug.com/issues/511935486: заведён и закрыт ClusterFuzz'ом в один день, ноль developer-взаимодействий. Фактический дубликат crbug.com/issues/511210507, но с другой crash-сигнатурой - ClusterFuzz не распознал совпадение. Project Zero решает проблему жёстким критерием: actionable = не дубликат + не WAI + минимум один fix-коммит, ссылающийся на баг. Критерий воспроизводимый, но заведомо занижает результат обеих систем.
Парадокс пересечения: 25% в продакшене, 60% в эксперименте
В продакшене 20 из 81 actionable бага найдены обеими системами - 25%. Чуть больше половины из этих 20 первым обнаружил Fuzzilli.Дедупликация нетривиальна. Project Zero описывает четыре уровня:
- Явные дубликаты в баг-трекере (crbug.com/issues/522410841 -> crbug.com/issues/522860268)
- "Нерабочие" репорты, привязанные к тому же fix-коммиту через цепочку зависимостей
- Дубликаты, отсечённые ещё на уровне ClusterFuzz до попадания в трекер
- Формально разные баги, исправленные одним CL (четыре бага в crrev.com/c/7965120 - скорее дубликаты, чем четыре независимые проблемы)
Результат:
- ~62% багов переоткрыты
- ~12% коммитов дали другой, уже исправленный баг
- ~26% - ничего
Почему 25% != 60%? Четыре фактора:
- Окно переоткрытия. В продакшене баг фиксится быстро, вторая система не успевает его найти до патча. В эксперименте BigSleep тестирует pre-fix build - ограничения нет.
- Разные билды и флаги. Production-конфигурации движка различаются, что влияет на воспроизводимость крэшей.
- Pipeline outages. Пропущенные коммиты из-за инфраструктурных проблем.
- Апгрейды модели. За полгода модель BigSleep обновлялась несколько раз; ранние версии, вероятно, находили меньше.
Отдельное наблюдение: пересечение заметно ниже для JIT-compiler багов, чем для остальных компонентов V8. Гипотеза Project Zero (не тестировалась): баги, которые одновременно нелокальны и требуют сложных тесткейсов, тяжелы для обоих подходов.
Три бага: анатомия различий между фаззером и LLM-агентом
Конкретные кейсы проясняют архитектурные причины расхождений лучше агрегированной статистики. Project Zero, предположительно, детально разобрал три уязвимости движка V8, каждая из которых показывает сильную сторону одного подхода.
Chrome Bug 449760249: BigSleep находит вариант через patch analysis
Исходная уязвимость (найденная Fuzzilli двумя годами ранее): некорректное удаление проверки на "дыру" (hole) для переменных в Temporal Dead Zone (TDZ). BigSleep нашёл вариант:labeled break внутри do-while, направленный на внешний switch, не уведомляет loop builder о прерывании. Hole-check элиминируется там, где не должна.Почему фаззер мимо: coverage feedback не вознаграждает комбинирование
labeled break + do-while + switch. Каждая конструкция по отдельности уже покрыта, новых edge'ей при их сочетании нет. Агент действует иначе: читает патч к исходному багу, перечисляет control-flow пути, находит непокрытый и целенаправленно строит PoC. Patch diffing через LLM - и он работает.Chrome Bug 443765373: усечение handler offset за пределами фаззера
Handler offset в V8 хранится в 28-битном поле - покрывает 256 МБ байткода. Но байткод может достигать 512 МБ. PoC: функция с ~500 000 операторовyield* -> offset усекается -> переход на невалидный адрес обработчика исключений.Фаззер технически способен сгенерировать такой тесткейс. На практике - нет: coverage-guided мутации не порождают полумиллионных программ, потому что нет инкрементального сигнала, мотивирующего наращивать размер. Для каждого нового
yield* edge coverage не меняется. Агент подходит иначе: видит 28-битное поле, вычисляет предел 2^28, сопоставляет с максимальным размером байткода и строит программу, вызывающую переполнение. Чистый reasoning о числовых границах.Chrome Bug 40076141: эмерджентный конфликт оптимизаций в Maglev
Два корректных по отдельности паттерна оптимизации в компиляторе Maglev: MaglevPhiRepresentationSelector решает, что untag можно сделать без проверки (потому что видит Smi-range), а graph builder ранее вставил checked untag, который теперь удаляется. Когда значение выходит за пределы Smi - unchecked path не ловит это, checked убран -> memory corruption.BigSleep не переоткрыл этот баг ни в одном эксперименте. Причина фундаментальна: баг не локализуется в одном коммите или функции. Он возникает из взаимодействия двух независимых оптимизаций в разных частях компилятора, каждая из которых корректна в изоляции. Commit-review видит каждый diff безопасным. Чтобы найти такое, нужно одновременно подобрать данные за пределами Smi-range и конфигурацию кода, активирующую оба паттерна. Это задача для exploration в пространстве программ, а не для reasoning об одном diff'е.
Меморизация, coverage plateau и путь к гибридам
LLM помнит баги или обобщает паттерны?
Закономерный вопрос: если модель обучена на публичных баг-трекерах Chromium, не "вспоминает" ли она уже известные уязвимости?Project Zero проверил: knowledge cutoff используемой модели Gemini (версия не раскрыта) предшествует периоду тестирования, эффективный cutoff по данным трекера - на несколько месяцев раньше. Для багов до cutoff'а статистически значимой меморизации не обнаружено. Продакшен-результат (45 actionable за полгода) включает исключительно баги в свежих коммитах, которых по определению нет в training data.
Дополнительное наблюдение: баги, на обнаружение которых Fuzzilli тратил больше времени, были слегка сложнее и для BigSleep. Эффект статистически значим, но невелик. Корреляция слабая - подходы не fail в одних и тех же местах. Project Zero рекомендует перепроверять меморизацию с каждой новой моделью: cutoff'ы смещаются, рано или поздно training data будет содержать почти всю историю V8.
StateLens: LLM-guided инструментация как третий путь
Фундаментальная проблема coverage-guided фаззинга V8 - plateau. Когда edge coverage насыщен, баги скрываются за разными runtime-состояниями при одинаковом control flow: JIT tier transitions, hidden class mutations, GC pressure. Edge coverage к этим различиям слеп.StateLens (arxiv-идентификатор не подтверждён на момент публикации) предлагает решение через три стадии:
- Analysis - LLM-агент на основе knowledge base (комментарии разработчиков, debug-ассерты, баг-репорты) через iterative call-graph и data-flow traversal идентифицирует семантически значимые состояния
- Instrumentation - синтезирует read-only probe'ы, проецирующие выбранные состояния в compact shared-memory bitmap
- Fuzzing - dual-feedback механизм: фаззер удерживает программы, открывающие новые комбинации состояний, даже при неизменном edge coverage
StateLens принципиально отличается от BigSleep: LLM используется не для PoC-генерации, а для инструментации. Exploration остаётся за мутационным движком. Semantic understanding от LLM определяет где искать, coverage-guided engine - как искать. Именно эта гибридная архитектура может закрыть gap между двумя "чистыми" парадигмами.
Сравнение фаззера и LLM-агента: решающие критерии
Когда запускать фаззер: таргет - JIT-компилятор (Turbofan, Maglev), баги из нелокальных взаимодействий. Есть compute для непрерывного прогона на десятках инстансов. Нужно покрытие каждого коммита в течение часов. Ищете memory corruption: type confusion, OOB, UAF.
Когда запускать LLM-агент: фокус на новых language features, parser changes, spec updates. Баг-классы логические - некорректная элиминация проверок, truncation, граничные условия. Daily commit-review pipeline достаточен. Готовы к triage overhead (~10 сырых репортов на 1 actionable).
Когда нужны оба: всегда, если ресурсы позволяют. При 25%-ном пересечении в продакшене отказ от одного подхода оставляет 30-40% actionable-уязвимостей без покрытия. Project Zero формулирует прямо: "vendors perform both fuzzing and agentic analysis of their code to maximize the number of vulnerabilities that get reported before they impact users""Разработчики проводят как фаззинг, так и агентный анализ своего кода, чтобы выявить максимальное число уязвимостей до того, как они затронут пользователей.".
Все обсуждают headline - 45 против 37, "ничья с лёгким перевесом BigSleep". На деле главная цифра в отчёте Project Zero - не эта. Главная цифра - 30-40% багов, недоступных BigSleep даже при 25 попытках на коммит в идеальных условиях, плюс зеркальная прослойка багов, которые LLM находит, а фаззер - нет. Парадигма "один инструмент закрывает V8" мертва. Один инструмент покрывает 60-70% поверхности. Треть уязвимостей остаётся.
Через пару лет чистый coverage-guided фаззинг без LLM-инструментации состояний будет выглядеть так же, как blackbox-фаззинг выглядит сейчас - работающий, но архаичный. Данные StateLens (70% прирост) показывают вектор: LLM не заменяет фаззер, а переводит его в пространство состояний, где edge coverage слеп. Но и чистые LLM-агенты упёрлись в потолок нелокальных взаимодействий, где reasoning об одном коммите недостаточен. Ближайшее будущее автоматизированного поиска 0-day - за трёхслойной архитектурой: LLM-analysis для инструментации + coverage-guided exploration для interaction bugs + commit-review agent для логических ошибок. Кто из вендоров первым соберёт этот pipeline в production - получит год-два форы. Если хочется понять, как type confusion и OOB эксплуатируются в живом движке - pwn-категория на HackerLab.pro (ХакерЛаб) даёт именно это, без подсказок.
AI-выжимка
по разделам
Тезисы статьи скоро
Статья читается полностью. Тезисы со ссылками на разделы появятся позже.
Содержание
Codeby Academy
Практика и мастерство
От основ до продвинутого — программы для практиков Codeby.
Перейти к курсу →
Продолжить чтение
Следующий разбор
Файлы PDF - внутренний формат и уязвимости
Комментарии
0