fuzzilli – это фаззер для javascript-движков от команды googleprojectzero. Его отличительная черта – это FuzzIL, промежуточный язык, который можно мутировать и затем транслировать в js. Этот язык обеспечивает мутированному js семантическую валидность: даже после нескольких раундов изменений в коде остается логика, с которой движок будет работать.
Движки, которые можно фаззить
В этом списке V8, Spidermonkey, XS и duktape уже поддерживают работу с fuzzilli. А остальные движки можно пропатчить и начать фаззинг, патчи включены в состав инструмента.
С помощью fuzzilli уже найдено много интересных багов типа OOB. Разберемся, как он устроен.
FuzzIL
FuzzIL – это промежуточное представление js, и самое интересное в нем – это мутаторы для FuzzIL. Как они генерируют валидный js-код?
Мутировать js-код можно на следующих уровнях:
- текст скрипта (изменение случайных байт)
- синтаксическое дерево AST (вырезать/вставлять случайное поддерево)
Первый способ не сильно отличается:
![]()
Движок просто отбросит все предлагаемые варианты.
Способ с деревьями лучше: мутатор производит синтаксически правильный js-код. Например, вот такой:

Но, несмотря на это, в логике ошибка все-таки есть: переменная y здесь используется до своего объявления. Это вызовет исключение, а возможный баг type confusion останется неотловленным.
FuzzIL создан для того, чтобы добавить третий уровень: мутирование промежуточного представления. Оно должно обеспечить не только синтаксическую валидность, но и семантическую. Мутаторы FuzzIL не генерируют один только валидный код, но очевидных ошибок не допускают.
Пример программы на FuzzIL:

У FuzzIL нет синтаксиса, но представлять программу как-то надо, поэтому этот пример написан на некотором псевдокоде.
Транслятор у FuzzIL есть, и этот же пример на js будет выглядеть так:

Или:

fuzzilli умеет транслировать и так и так.
Чтобы описать мутаторы, нужно немного поговорить о реализации FuzzIL.
Реализация
fuzzilli и FuzzIL написаны на языке Swift.
Проиллюстрировать классы, которые реализуют программу на FuzzIL, поможет диаграмма:

Типом данных, отвечающим за итоговый js -скрипт, является class Program.
Программа – это набор операций вместе с их входными и выходными переменными.
Например, операция LoadInteger:

LoadProperty:

Посмотреть все операции можно в Operations.swift
За создание программы отвечают ProgramBuilder и CodeGenerators. Каждый генератор производит свой специфический фрагмент FuzzIL кода. Например, IntegerGenerator создает инструкцию LoadInteger со случайным параметром, а IfElseGenerator – условный блок:

Таких генераторов десятки. Некоторые из них вызывают другие генераторы, как IfElseGenerator, чтобы код внутри блока получился интереснее.
Когда новая программа создана, она подвергается анализу через:
VariableAnalyzerScopeAnalyzerContextAnalyzerDeadCodeAnalyzer
Проверяются переменные, их типы и области видимости. Учитывается, что переменные внутри конструкций for, while не должны быть видны снаружи. А переменные, определенные снаружи, должны быть видны внутри, если используются:
![]()
Проверяются блоки: если есть операция BeginWhileLoop, то должен быть и EndWhileLoop. И наоборот: если есть конец блока EndWhileLoop, то должно быть и начало BeginWhileLoop.
Типы: Unknown, Integer, Float, String, Boolean, Object, Function. Проверка типов не допустит, например, такой код:
![]()
Не допускается dead код, чтобы предотвратить бессмысленное разрастание тест-кейсов.
После всех проверок код транслируется в текст. За трансляцию отвечает class JavaScriptLifter. В этот класс заложены правила того, как представлять инструкции в текстовом виде.

Мутаторы
- input mutator
- operation mutator
- combine mutator
- splice mutator
Input mutator
Мутирует входные аргументы-переменные.

Учитывается область видимости; переменная v12 из того же скоупа, что и все остальные.
Если невозможно найти замену, то кандидат вернется на свое место.
Operation Mutator
Мутирует параметр операций, если он у них есть.
У объектов операций помимо входных и выходных переменных есть еще и параметры. Например, у LoadInteger это value, у LoadProperty – propertyName, а у CallMethod – methodName.
Combine Mutator

Splice Mutator
Вставка куска кода из одного тест-кейса в другой. Учитывается, что все переменные в коде должны быть определены. Алгоритм такой:
- выбрать рандомную инструкцию;
- найти все инструкции, чьи выходные значения используются для этой рандомной; инструкции;
- скопировать, вставить.
Инструментация
Между фаззером и процессом движка создается shm область памяти, в которой хранится информация о покрытии.
Покрытие – это битмап, фаззеру интересен только факт посещения базового блока, а сколько раз поток прошел через этот блок уже неинтересно.
В каждом базовом блоке размещается вызов sanitizer_cov_trace_pc_guard:

Здесь guard – указатель на индекс блока, __shmem->edges – массив, с которым работают, как с битмапом.
Как уже упоминалось, часть движков поддерживает инструментацию fuzzilli. Чтобы инструментировать, например, v8, нужно указывать флаг v8_fuzzilli=true в процессе сборки v8. К счастью, сборка всех движков автоматизирована и помнить о флагах не нужно – в составе есть скрипты для этого. А если инструментация не поддерживается, то там же есть и патчи.
Исполнение скрипта
Для увеличения производительности фаззинга необходимо сокращать затраты на создание и запуск процесса, в котором будет исполняться исследуемый код. Для этого существует два механизма:
- форк-сервер, как в AFL: размещается в процессе таргета, получает очередную порцию мусора от мастера и делает форк, в котором идет обработка. В этом случае переиспользуется все, что до форк-сервера.
- REPRL(read-eval-print-reset-loop), как в libfuzzer: в бесконечном цикле исследуемый код ждет и получает данные, обрабатывает, потом сбрасывает состояние на начальное, и по-новой. Здесь переиспользуется все, что до цикла.
Преимущество форк-сервера в том, что его можно разместить где угодно (пример из AFL). REPRL-цикл постоянно на одном месте: (пример V8).
Но производительность вроде бы за REPRL.
Авторы fuzzilli провели измерения для движка JavaScriptCore и оказалось, что REPRL опережает форк-сервер на 6 мс за одну итерацию. Тестом был скрипт, который просто ждет 1 секунду: результат REPRL – 1,001 c, а форк-сервер – 1,007 c. Тест кажется простым, но тем не менее был выбран REPRL.
Как и инструментация, реализация REPRL включена в состав конкретного движка, если он поддерживает работу с fuzzilli.
CVE-2019-8518
Это OOB—баг, найденный в JavaScriptCore/webkit:

Вот как JIT-компилятор оптимизирует код:
- нода
CheckStructureизначально проверяет тип массива в теле цикла, однако ее можно вынести, потому что тип массива один и тот же; CheckBounds– остается, потому что есть зависимость от длины массива;GetByVal– выносится, так как не зависит от переменных, определенных внутри цикла.

Поэтому можно читать и писать за пределы массива.
Эксплуатация багов в js-движках – это отдельный жанр. Баг выше было легко описать, поэтому он здесь. Другие находки fuzzilli тоже интересны, но они слишком заковыристые, и их не уместить в пару абзацев.
Вывод
fuzzilli – это фаззер для js-движков, который обеспечивает большее покрытие кода, потому что генерирует и синтаксически и семантически валидный код, то есть настоящие js-скрипты. В этой статье мы разобрали, как оно работает.
Еще больше деталей есть в трудах:
FuzzIL: Coverage Guided Fuzzing for JavaScript EnginesThe hunt for Chromium issue 1072171Fuzzing JavaScript Engines with Fuzzilli
Если занимаетесь поиском багов в js, может пригодиться.
Поэтому можно читать и писать за пределы массива.
Эксплуатация багов в js-движках – это отдельный жанр. Баг выше было легко описать, поэтому он здесь. Другие находки fuzzilli тоже интересны, но они слишком заковыристые, и их не уместить в пару абзацев.
Вывод
fuzzilli – это фаззер для js-движков, который обеспечивает большее покрытие кода, потому что генерирует и синтаксически и семантически валидный код, то есть настоящие js-скрипты. В этой статье мы разобрали, как оно работает.
Еще больше деталей есть в трудах:
FuzzIL: Coverage Guided Fuzzing for JavaScript EnginesThe hunt for Chromium issue 1072171Fuzzing JavaScript Engines with Fuzzilli
Если занимаетесь поиском багов в js, может пригодиться.
Марат Гаянов