Fuzzilli
(Покрытие-)направляемый фаззер для интерпретаторов динамических языков, основанный на пользовательском промежуточном языке («FuzzIL»), который можно мутировать и транслировать в JavaScript.
Использование
Основные шаги для использования этого фаззера:
- Загрузите исходный код одного из поддерживаемых движков JavaScript. Список поддерживаемых движков JavaScript см. в директории Targets/.
- Примените соответствующие патчи из директории целевого движка. Также см. README.md в этой директории.
- Скомпилируйте движок с инструментацией покрытия (требуется clang >= 4.0), как описано в README.
- Скомпилируйте фаззер:
swift build [-c release].
- Запустите фаззер:
swift run [-c release] FuzzilliCli --profile=<profile> [other cli options] /path/to/jsshell. См. также swift run FuzzilliCli --help.
Сборка и запуск Fuzzilli и поддерживаемых движков JavaScript внутри Docker и на Google Compute Engine также поддерживаются.
Хакинг
Посмотрите main.swift, чтобы увидеть пример использования библиотеки Fuzzilli и поиграть с различными параметрами конфигурации. Затем ознакомьтесь с Fuzzer.swift для высокоуровневой логики фаззинга. Оттуда погружайтесь в любую интересную часть.
Патчи, дополнения и другие вклады в этот проект приветствуются! Однако предварительно ознакомьтесь с заметками для контрибьюторов. Fuzzilli в целом следует стилю кода Google для Swift.
Будет очень признательно, если вы отправите короткое уведомление (возможно, с номером CVE) на [email protected] или откроете pull request для любой уязвимости, найденной с помощью этого проекта, чтобы она могла быть включена в раздел галерея багов. В остальном вы, конечно, можете претендовать на любые вознаграждения за баги, кредиты CVE и т.д. за эти уязвимости :)
Концепция
При фаззинге на предмет основных ошибок интерпретатора, например в JIT-компиляторах, семантическая корректность генерируемых программ становится проблемой. Это отличается от большинства других сценариев, например фаззинга API времени выполнения, где семантическую некорректность можно легко обойти, обернув сгенерированный код в конструкции try-catch. Существуют разные способы достижения приемлемого уровня семантически корректных образцов, один из них — мутационный подход, при котором все образцы в корпусе также семантически валидны. В этом случае каждая мутация имеет лишь небольшой шанс превратить валидный образец в невалидный.
Для реализации мутационного JavaScript-фаззера необходимо определить мутации кода JavaScript. Вместо мутации AST или других синтаксических элементов программы определяется пользовательский промежуточный язык (IL), на котором мутации потока управления и данных программы могут выполняться более непосредственно. Затем этот IL транслируется в JavaScript для выполнения. Промежуточный язык выглядит примерно так:
v0 <− LoadInteger '0'
v1 <− LoadInteger '10'
v2 <− LoadInteger '1'
v3 <− LoadInteger '0'
BeginFor v0, '<', v1, '+', v2 −> v4
v6 <− BinaryOperation v3, '+', v4
Reassign v3, v6
EndFor
v7 <− LoadString 'Result: '
v8 <− BinaryOperation v7, '+', v3
v9 <− LoadGlobal 'console'
v10 <− CallMethod v9, 'log', [v8]
Который, например, может быть тривиально транслирован в следующий JavaScript-код:
const v0 = 0;
const v1 = 10;
const v2 = 1;
let v3 = 0;
for (let v4 = v0; v4 < v1; v4 = v4 + v2) {
const v6 = v3 + v4;
v3 = v6;
}
const v7 = "Result: ";
const v8 = v7 + v3;
const v9 = console;
const v10 = v9.log(v8);
Или в следующий JavaScript-код путём встраивания промежуточных выражений:
let v3 = 0;
for (let v4 = 0; v4 < 10; v4++) {
v3 = v3 + v4;
}
console.log("Result: " + v3);
FuzzIL обладает рядом свойств:
- Программа FuzzIL — это просто список инструкций.
- Инструкция FuzzIL — это операция вместе с входными и выходными переменными и, возможно, одним или несколькими параметрами (обозначается в одинарных кавычках в записи выше).
- Входными данными для инструкций всегда являются переменные, никаких непосредственных значений.
- Каждый выход инструкции — это новая переменная, и существующие переменные могут быть переприсвоены только через специальные операции, такие как инструкция
Reassign.
- Каждая переменная определена до её использования.
Над этими программами могут выполняться различные мутации:
- InputMutator: заменяет входные переменные инструкций на другие, мутируя поток данных программы.
- CodeGenMutator: генерирует код и вставляет его в мутированную программу. Код генерируется либо путем выполнения генератора кода, либо копированием некоторых инструкций из другой программы в корпусе (сплайсинг).
- CombineMutator: вставляет программу из корпуса в случайную позицию мутированной программы.
- OperationMutator: мутирует параметры операций, например заменяет целочисленную константу на другую.
- и другие...
Более подробное обсуждение того, как работает Fuzzilli, можно найти здесь.
Реализация
Фаззер реализован на Swift, некоторые части (например, измерение покрытия, сокетные взаимодействия и т.д.) реализованы на C.
Архитектура
Экземпляр фаззера (реализован в Fuzzer.swift) состоит из следующих центральных компонентов:
- MutationFuzzer: создает новые программы из существующих, применяя мутации. Затем выполняет полученные образцы и оценивает их.
- ScriptRunner: выполняет программы целевого языка.
- Corpus: хранит интересные образцы и предоставляет их ядру фаззера.
- Environment: содержит знания о среде выполнения, например о доступных встроенных объектах, именах свойств и методов.
- Minimizer: минимизирует аварийные и интересные программы.
- Evaluator: оценивает, является ли образец интересным по какому-либо критерию, например покрытию кода.
- Lifter: транслирует программу FuzzIL в целевой язык (JavaScript).
Кроме того, опционально доступны несколько модулей:
- Statistics: собирает различную статистическую информацию.
- NetworkSync: синхронизирует несколько экземпляров по сети.
- ThreadSync: синхронизирует несколько экземпляров в рамках одного процесса.
- Storage: сохраняет аварийные программы на диск.
Фаззер управляется событиями, причем большинство взаимодействий между различными классами происходит через события. События генерируются, например, при краше, при нахождении интересной программы, при выполнении новой программы, при генерации лог-сообщения и т.д. Полный список событий см. в Events.swift. Механизм событий эффективно развязывает различные компоненты фаззера и упрощает реализацию дополнительных модулей.
Программа FuzzIL может быть построена с помощью экземпляра ProgramBuilder. ProgramBuilder предоставляет методы для создания и добавления новых инструкций, добавления инструкций из другой программы, получения существующих переменных, запроса контекста выполнения в текущей позиции (например, находится ли он внутри цикла) и многое другое.
Выполнение
Fuzzilli использует пользовательский режим выполнения под названием REPRL (read-eval-print-reset-loop). Для этого целевой движок модифицируется для приёма входного скрипта через каналы и/или разделяемую память, его выполнения, затем сброса своего внутреннего состояния и ожидания следующего скрипта. Это устраняет накладные расходы на создание процесса и в значительной степени на инициализацию движка.
Масштабируемость
Существует один экземпляр Fuzzer на целевой процесс. Это обеспечивает синхронное выполнение программ и тем самым упрощает реализацию различных алгоритмов, таких как последовательные мутации и минимизация. Кроме того, это избавляет от необходимости реализовывать потокобезопасный доступ к внутреннему состоянию, например к корпусу. Каждый экземпляр фаззера имеет свою собственную DispatchQueue, концептуально соответствующую одному потоку. Как правило, любое взаимодействие с экземпляром Fuzzer должно происходить в очереди этого экземпляра. Это гарантирует потокобезопасность, так как очередь последовательная. Более подробно см. документацию.
Для масштабирования экземпляры фаззера могут образовывать иерархию деревьев, в которой они сообщают об обнаруженных новых интересных образцах и крашах родительскому узлу. В свою очередь, родительский узел синхронизирует свой корпус с дочерними узлами. Связь между узлами в дереве может осуществляться разными способами, каждый из которых реализован в виде модуля:
- Межпоточная связь: синхронизирует экземпляры в одном процессе путем постановки задач в очередь DispatchQueue другого фаззера.
- Межмашинная связь: синхронизирует экземпляры по простому протоколу на основе TCP.
Такая конструкция позволяет фаззеру масштабироваться на множество ядер на одной машине, а также на множество разных машин. Поскольку один родительский узел может быстро перегрузиться, если слишком много экземпляров отправляют ему программы, можно настроить несколько уровней экземпляров, например один корневой экземпляр, 16 промежуточных узлов, подключенных к корню, и 256 «листьев», подключенных к промежуточным узлам. См. директорию Cloud/ для получения дополнительной информации о распределенном фаззинге.
Ресурсы
Дополнительные ресурсы об этом фаззере:
- Презентация о Fuzzilli, представленная на Offensive Con 2019.
- Дипломная работа, в рамках которой была выполнена первоначальная реализация.
- Статья в блоге от Sensepost об использовании Fuzzilli для поиска бага в v8.
- Статья в блоге от Doyensec о фаззинге движка JerryScript с помощью Fuzzilli.
- Статья с симпозиума NDSS 2023 о Fuzzilli и его сравнении с другими фаззерами.
Галерея багов
Ниже приведен список некоторых багов, найденных с помощью Fuzzilli. В этот список должны включаться только баги с влиянием на безопасность, которые присутствовали как минимум в бета-версии соответствующего ПО. Поскольку Fuzzilli часто используется для непрерывного фазз-тестирования во время разработки, многие проблемы, найденные им, не включены в этот список, так как они обычно обнаруживаются до того, как уязвимый код достигает бета-версии. Список всех проблем, недавно найденных Fuzzilli в V8, можно найти здесь.
Особая благодарность всем пользователям Fuzzilli, сообщившим о найденных с его помощью багах!
WebKit/JavaScriptCore
- Issue 185328: DFG Compiler использует неправильный выходной регистр для операции NumberIsInteger
- CVE-2018-4299: performProxyCall раскрывает внутренний объект скрипту
- CVE-2018-4359: compileMathIC генерирует некорректный машинный код
- CVE-2019-8518: Выход за границы в FTL JIT из-за того, что LICM перемещает доступ к массиву перед проверкой границ
- CVE-2019-8558: Use-after-free CodeBlock из-за зависших Watchpoints
- CVE-2019-8611: Оптимизация AIR некорректно удаляет присваивание регистру
- CVE-2019-8623: Loop-invariant code motion (LICM) в DFG JIT оставляет стековую переменную неинициализированной
- CVE-2019-8622: doesGC() в DFG некорректен относительно поведения операции HasIndexedProperty на StringObjects.
- CVE-2019-8671: DFG: Loop-invariant code motion (LICM) оставляет доступ к свойству объекта незащищенным
- CVE-2019-8672: Use-after-free JSValue в ValueProfiles
- CVE-2019-8678: JSC не вызывает haveABadTime() при изменении некоторых прототипов, что приводит к путанице типов
- CVE-2019-8685: JSPropertyNameEnumerator использует неверные ID структур
- CVE-2019-8765: Путаница типов GetterSetter во время компиляции DFG
- CVE-2019-8820: Путаница типов при выходе из JIT при восстановлении объектов аргументов
- CVE-2019-8844: ObjectAllocationSinkingPhase не должна вставлять хинты для размещений, которые больше не действительны
Gecko/Spidermonkey
- CVE-2018-12386: Ошибка распределения регистров в IonMonkey приводит к путанице типов
- CVE-2019-9791: Вывод типов IonMonkey неверен для конструкторов, вызванных через OSR
- CVE-2019-9792: IonMonkey раскрывает магическое значение JS_OPTIMIZED_OUT скрипту
- CVE-2019-9816: Неожиданная ObjectGroup в операции ObjectGroupDispatch
- CVE-2019-9813: Скомпилированный код IonMonkey не обновляет выведенные типы свойств, что приводит к путанице типов
- CVE-2019-11707: IonMonkey неверно предсказывает возвращаемый тип Array.prototype.pop, что приводит к путанице типов
- CVE-2020-15656: Путаница типов для специальных аргументов в IonMonkey
- CVE-2021-29982: Некорректное распределение регистров (найдено JIT-Picker)
- CVE-2021-29984: Переупорядочивание инструкций в сочетании с неожиданным GC может привести к повреждению памяти
- CVE-2022-28285: AliasSet для MLoadTypedArrayElementHole слишком разрешительный
- CVE-2022-31745: Ошибка в инкрементальном GC
- CVE-2022-42928: Отсутствие аннотаций KeepAlive для некоторых операций с BigInt может привести к повреждению памяти
- CVE-2022-45406: Use-after-free объекта JavaScript Realm
- CVE-2023-4577: Повреждение памяти из-за взаимодействия GC и RegEx
- : GC привел к состоянию use-after-free во время компиляции
Chromium/v8* Issue 939316: Turbofan может читать указатель Map за пределами границ при оптимизации Reflect.construct
- Issue 944062: JSCallReducer::ReduceArrayIndexOfIncludes не вставляет проверки Map
- CVE-2019-5831: Некорректная обработка Map в V8
- Issue 944865: Некорректное представление значений в V8
- CVE-2019-5841: Ошибка в эвристике встраивания (inlining)
- CVE-2019-5847: Запечатанные/замороженные элементы V8 вызывают аварию
- CVE-2019-5853: Повреждение памяти при проверке длины регулярного выражения
- Issue 992914: Миграция Map не учитывает виды элементов, что приводит к путанице типов
- CVE-2020-6512: Путаница типов в V8
- CVE-2020-16006: Повреждение памяти из-за некорректной обработки коллизий хешей в DescriptorArray
- CVE-2021-37991: Состояние гонки при одновременной JIT-компиляции
- Issue 1359937: Десериализация BigInt может приводить к недопустимому значению -0n
- Issue 1377775: Некорректная проверка типа при встраивании Array.prototype.at в Turbofan
- Issue 2323: Нестабильный указатель valstack в putprop
- Issue 2320: Переполнение указателя в memcmp в строковой встроенной функции
- CVE-2020-13991: Некорректное освобождение развёрнутых аргументов (spread arguments)
- Issue 3784: Повреждение памяти из-за некорректного перечисления свойств
- CVE-2020-13623: Переполнение стека через ключи свойств для объектов Proxy
- CVE-2020-13649 (1): Повреждение памяти из-за обработки ошибок при нехватке памяти (OOM)
- CVE-2020-13649 (2): Повреждение памяти из-за обработки ошибок при нехватке памяти (OOM)
- CVE-2020-13622: Повреждение памяти из-за некорректной обработки ключей свойств для объектов Proxy
- CVE-2020-14163: Повреждение памяти из-за состояния гонки, вызванного сборкой мусора при добавлении пар ключ/значение
- Issue 3813: Некорректная обработка ошибок в функции SerializeJSONProperty
- Issue 3814: Неожиданный объект Proxy в утверждении ecma_op_function_has_instance
- Issue 3836: Повреждение памяти из-за некорректной инициализации TypedArray
- Issue 3837: Повреждение памяти из-за некорректного управления памятью в getOwnPropertyDescriptor
- CVE-2020-1912: Повреждение памяти при выполнении лениво скомпилированных внутренних функций-генераторов
- CVE-2020-1914: Повреждение байт-кода при обработке инструкции SaveGeneratorLong
Отказ от ответственности
Это неофициальный продукт, поддерживаемый Google.