fuzzilli – это фаззер для javascript-движков от команды googleprojectzero. Его отличительная черта – это FuzzIL, промежуточный язык, который можно мутировать и затем транслировать в js. Этот язык обеспечивает мутированному js семантическую валидность: даже после нескольких раундов изменений в коде остается логика, с которой движок будет работать. В этом списке V8, Spidermonkey, XS и duktape уже поддерживают работу с fuzzilli. А остальные движки можно пропатчить и начать фаззинг, патчи включены в состав инструмента. С помощью fuzzilli уже найдено много интересных багов типа OOB. Разберемся, как он устроен. 1. FuzzIL
2. Реализация
3. Мутаторы
3.1. Input mutator
3.2. Operation Mutator
3.3. Combine Mutator
3.4. Splice Mutator
4. Инструментация
5. Исполнение скрипта
6. CVE-2019-8518
7. Вывод FuzzIL – это промежуточное представление js, и самое интересное в нем – это мутаторы для FuzzIL. Как они генерируют валидный js-код? Мутировать js-код можно на следующих уровнях: Первый способ не сильно отличается: Движок просто отбросит все предлагаемые варианты. Способ с деревьями лучше: мутатор производит синтаксически правильный 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, чтобы код внутри блока получился интереснее. Когда новая программа создана, она подвергается анализу через: Проверяются переменные, их типы и области видимости. Учитывается, что переменные внутри конструкций for, while не должны быть видны снаружи. А переменные, определенные снаружи, должны быть видны внутри, если используются: Проверяются блоки: если есть операция BeginWhileLoop, то должен быть и EndWhileLoop. И наоборот: если есть конец блока EndWhileLoop, то должно быть и начало BeginWhileLoop. Типы: Unknown, Integer, Float, String, Boolean, Object, Function. Проверка типов не допустит, например, такой код: Не допускается dead код, чтобы предотвратить бессмысленное разрастание тест-кейсов. После всех проверок код транслируется в текст. За трансляцию отвечает class JavaScriptLifter. В этот класс заложены правила того, как представлять инструкции в текстовом виде. Мутирует входные аргументы-переменные. Учитывается область видимости; переменная v12 из того же скоупа, что и все остальные.
Если невозможно найти замену, то кандидат вернется на свое место. Мутирует параметр операций, если он у них есть. У объектов операций помимо входных и выходных переменных есть еще и параметры. Например, у LoadInteger это value, у LoadProperty – propertyName, а у CallMethod – methodName. Вставка куска кода из одного тест-кейса в другой. Учитывается, что все переменные в коде должны быть определены. Алгоритм такой: Между фаззером и процессом движка создается 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. Это OOB-баг, найденный в JavaScriptCore/webkit: IR-представление функции v7 будет выглядеть примерно так (взято из отчета): А вот как JIT-компилятор оптимизирует код: Поэтому можно читать и писать за пределы массива. Эксплуатация багов в js-движках – это отдельный жанр. Баг выше было легко описать, поэтому он здесь. Другие находки fuzzilli тоже интересны, но они слишком заковыристые, и их не уместить в пару абзацев. Поэтому здесь они приведены списком. fuzzilli – это фаззер для js-движков, который обеспечивает большее покрытие кода, потому что генерирует и синтаксически и семантически валидный код, то есть настоящие js-скрипты. В этой статье мы разобрали, как оно работает.  Еще больше деталей есть в трудах: Если занимаетесь поиском багов в js, может пригодиться. Program received signal SIGSEGV