Fuzzilli
Um fuzzer guiado por cobertura para interpretadores de linguagens dinâmicas, baseado em uma linguagem intermediária personalizada ("FuzzIL") que pode ser mutada e traduzida para JavaScript.
Uso
Os passos básicos para usar este fuzzer são:
- Baixe o código-fonte de um dos mecanismos JavaScript suportados. Consulte o diretório Targets/ para a lista de mecanismos JavaScript suportados.
- Aplique os patches correspondentes do diretório do alvo. Consulte também o README.md nesse diretório.
- Compile o mecanismo com instrumentação de cobertura (requer clang >= 4.0) conforme descrito no README.
- Compile o fuzzer:
swift build [-c release].
- Execute o fuzzer:
swift run [-c release] FuzzilliCli --profile=<profile> [other cli options] /path/to/jsshell. Veja também swift run FuzzilliCli --help.
Construir e executar o Fuzzilli e os mecanismos JavaScript suportados dentro do Docker e no Google Compute Engine também é suportado.
Hacking
Confira main.swift para ver um exemplo de uso da biblioteca Fuzzilli e brincar com as várias opções de configuração. Em seguida, dê uma olhada em Fuzzer.swift para a lógica de fuzzing de alto nível. A partir daí, mergulhe em qualquer parte que pareça interessante.
Patches, adições e outras contribuições a este projeto são muito bem-vindas! No entanto, verifique rapidamente as notas para contribuidores. O Fuzzilli segue aproximadamente o guia de estilo de código do Google para Swift.
Seria muito apreciado se você pudesse enviar uma breve nota (possivelmente incluindo um número CVE) para [email protected] ou abrir um pull request para qualquer vulnerabilidade encontrada com a ajuda deste projeto, para que possa ser incluída na seção mostruário de bugs. Fora isso, você pode, claro, reivindicar qualquer recompensa por bug, créditos CVE, etc. pelas vulnerabilidades :)
Conceito
Ao fazer fuzzing para bugs no núcleo do interpretador, por exemplo em compiladores JIT, a correção semântica dos programas gerados torna-se uma preocupação. Isso contrasta com a maioria dos outros cenários, por exemplo, fuzzing de APIs de tempo de execução, onde a correção semântica pode ser facilmente contornada envolvendo o código gerado em construções try-catch. Existem diferentes possibilidades para atingir uma taxa aceitável de amostras semanticamente corretas, uma delas sendo uma abordagem mutacional na qual todas as amostras no corpus também são semanticamente válidas. Nesse caso, cada mutação tem apenas uma pequena chance de transformar uma amostra válida em uma inválida.
Para implementar um fuzzer JavaScript baseado em mutação, mutações no código JavaScript precisam ser definidas. Em vez de mutar a AST ou outros elementos sintáticos de um programa, uma linguagem intermediária (IL) personalizada é definida, na qual mutações no fluxo de controle e dados de um programa podem ser realizadas mais diretamente. Esta IL é posteriormente traduzida para JavaScript para execução. A linguagem intermediária parece aproximadamente como a seguir:
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]
Que pode, por exemplo, ser traduzido trivialmente para o seguinte código 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);
Ou para o seguinte código JavaScript, embutindo expressões intermediárias:
let v3 = 0;
for (let v4 = 0; v4 < 10; v4++) {
v3 = v3 + v4;
}
console.log("Result: " + v3);
O FuzzIL tem várias propriedades:
- Um programa FuzzIL é simplesmente uma lista de instruções.
- Uma instrução FuzzIL é uma operação juntamente com variáveis de entrada e saída e potencialmente um ou mais parâmetros (entre aspas simples na notação acima).
- As entradas para instruções são sempre variáveis, não há valores imediatos.
- Cada saída de uma instrução é uma nova variável, e as variáveis existentes só podem ser reatribuídas por meio de operações dedicadas, como a instrução
Reassign.
- Cada variável é definida antes de ser usada.
Uma série de mutações pode então ser realizada nesses programas:
- InputMutator: substitui variáveis de entrada das instruções por outras diferentes para mutar o fluxo de dados do programa.
- CodeGenMutator: gera código e o insere em algum lugar no programa mutado. O código é gerado executando um gerador de código ou copiando algumas instruções de outro programa no corpus (splicing).
- CombineMutator: insere um programa do corpus em uma posição aleatória no programa mutado.
- OperationMutator: muta os parâmetros das operações, por exemplo, substituindo uma constante inteira por outra diferente.
- e mais...
Uma discussão muito mais aprofundada de como o Fuzzilli funciona pode ser encontrada aqui.
Implementação
O fuzzer é implementado em Swift, com algumas partes (por exemplo, medições de cobertura, interações de socket, etc.) implementadas em C.
Arquitetura
Uma instância do fuzzer (implementada em Fuzzer.swift) é composta pelos seguintes componentes centrais:
- MutationFuzzer: produz novos programas a partir de programas existentes aplicando mutações. Em seguida, executa as amostras produzidas e as avalia.
- ScriptRunner: executa programas da linguagem alvo.
- Corpus: armazena amostras interessantes e as fornece ao fuzzer central.
- Environment: tem conhecimento do ambiente de tempo de execução, por exemplo, os builtins disponíveis, nomes de propriedades e métodos.
- Minimizer: minimiza programas que causam crash e programas interessantes.
- Evaluator: avalia se uma amostra é interessante de acordo com alguma métrica, por exemplo, cobertura de código.
- Lifter: traduz um programa FuzzIL para a linguagem alvo (JavaScript).
Além disso, uma série de módulos estão opcionalmente disponíveis:
- Statistics: coleta várias informações estatísticas.
- NetworkSync: sincroniza várias instâncias pela rede.
- ThreadSync: sincroniza várias instâncias dentro do mesmo processo.
- Storage: armazena programas que causam crash em disco.
O fuzzer é orientado a eventos, com a maioria das interações entre diferentes classes ocorrendo por meio de eventos. Eventos são despachados, por exemplo, como resultado de um crash ou de um programa interessante ser encontrado, um novo programa ser executado, uma mensagem de log ser gerada, e assim por diante. Veja Events.swift para a lista completa de eventos. O mecanismo de eventos efetivamente desacopla os vários componentes do fuzzer e facilita a implementação de módulos adicionais.
Um programa FuzzIL pode ser construído usando uma instância de ProgramBuilder. Um ProgramBuilder fornece métodos para criar e anexar novas instruções, anexar instruções de outro programa, recuperar variáveis existentes, consultar o contexto de execução na posição atual (por exemplo, se está dentro de um loop), e mais.
Execução
O Fuzzilli usa um modo de execução personalizado chamado REPRL (read-eval-print-reset-loop). Para isso, o mecanismo alvo é modificado para aceitar uma entrada de script por pipes e/ou memória compartilhada, executá-la, depois redefinir seu estado interno e aguardar o próximo script. Isso remove a sobrecarga da criação de processos e, em grande parte, da inicialização do mecanismo.
Escalabilidade
Há uma instância de Fuzzer por processo alvo. Isso permite a execução síncrona de programas e, portanto, simplifica a implementação de vários algoritmos, como mutações consecutivas e minimização. Além disso, evita a necessidade de implementar acesso thread-safe ao estado interno, por exemplo, ao corpus. Cada instância do fuzzer tem sua própria DispatchQueue, correspondendo conceitualmente a uma única thread. Como regra geral, toda interação com uma instância do Fuzzer deve ocorrer na fila de despacho dessa instância. Isso garante thread-safety, pois a fila é serial. Para mais detalhes, veja a documentação.
Para escalar, as instâncias do fuzzer podem formar uma hierarquia de árvore, caso em que relatam novas amostras interessantes e crashes encontrados ao seu nó pai. Por sua vez, um nó pai sincroniza seu corpus com seus nós filhos. A comunicação entre nós na árvore pode ocorrer de diferentes formas, cada uma implementada como um módulo:
Este design permite que o fuzzer escale para muitos núcleos em uma única máquina, bem como para muitas máquinas diferentes. Como um nó pai pode rapidamente ficar sobrecarregado se muitas instâncias enviarem programas para ele, é possível configurar múltiplos níveis de instâncias, por exemplo, uma instância raiz, 16 nós intermediários conectados à raiz e 256 "folhas" conectadas aos nós intermediários. Consulte o diretório Cloud/ para mais informações sobre fuzzing distribuído.
Recursos
Recursos adicionais sobre este fuzzer:
- Uma apresentação sobre o Fuzzilli dada na Offensive Con 2019.
- A tese de mestrado para a qual a implementação inicial foi feita.
- Um post de blog da Sensepost sobre o uso do Fuzzilli para encontrar um bug no V8.
- Um post de blog da Doyensec sobre fuzzing do mecanismo JerryScript com o Fuzzilli.
- Um artigo do Simpósio NDSS 2023 sobre o Fuzzilli e como ele se compara a outros fuzzers.
Mostruário de Bugs
A seguir está uma lista de alguns bugs encontrados com a ajuda do Fuzzilli. Apenas bugs com impacto de segurança que estavam presentes em pelo menos uma versão Beta do software afetado devem ser incluídos nesta lista. Como o Fuzzilli é frequentemente usado para testes de fuzzing contínuos durante o desenvolvimento, muitos problemas encontrados por ele não estão incluídos nesta lista, pois geralmente são encontrados antes do código vulnerável atingir uma versão Beta. Uma lista de todos os problemas recentemente encontrados pelo Fuzzilli no V8 pode, no entanto, ser encontrada aqui.
Agradecimentos especiais a todos os usuários do Fuzzilli que relataram bugs encontrados por ele!
WebKit/JavaScriptCore
- Issue 185328: DFG Compiler uses incorrect output register for NumberIsInteger operation
- CVE-2018-4299: performProxyCall leaks internal object to script
- CVE-2018-4359: compileMathIC produces incorrect machine code
- CVE-2019-8518: OOB access in FTL JIT due to LICM moving array access before the bounds check
- CVE-2019-8558: CodeBlock UaF due to dangling Watchpoints
- CVE-2019-8611: AIR optimization incorrectly removes assignment to register
- CVE-2019-8623: Loop-invariant code motion (LICM) in DFG JIT leaves stack variable uninitialized
- CVE-2019-8622: DFG's doesGC() is incorrect about the HasIndexedProperty operation's behaviour on StringObjects
- CVE-2019-8671: DFG: Loop-invariant code motion (LICM) leaves object property access unguarded
- CVE-2019-8672: JSValue use-after-free in ValueProfiles
- CVE-2019-8678: JSC fails to run haveABadTime() when some prototypes are modified, leading to type confusions
- CVE-2019-8685: JSPropertyNameEnumerator uses wrong structure IDs
- CVE-2019-8765: GetterSetter type confusion during DFG compilation
- CVE-2019-8820: Type confusion during bailout when reconstructing arguments objects
- CVE-2019-8844: ObjectAllocationSinkingPhase shouldn't insert hints for allocations which are no longer valid
Gecko/Spidermonkey
- CVE-2018-12386: IonMonkey register allocation bug leads to type confusions
- CVE-2019-9791: IonMonkey's type inference is incorrect for constructors entered via OSR
- CVE-2019-9792: IonMonkey leaks JS_OPTIMIZED_OUT magic value to script
- CVE-2019-9816: unexpected ObjectGroup in ObjectGroupDispatch operation
- CVE-2019-9813: IonMonkey compiled code fails to update inferred property types, leading to type confusions
- CVE-2019-11707: IonMonkey incorrectly predicts return type of Array.prototype.pop, leading to type confusions
- CVE-2020-15656: Type confusion for special arguments in IonMonkey
- CVE-2021-29982: Incorrect register allocation (found by JIT-Picker)
- CVE-2021-29984: Instruction reordering in combination with an unexpected GC may lead to memory corruption
- CVE-2022-28285: AliasSet for MLoadTypedArrayElementHole to permissive
- CVE-2022-31745: Error in incremental GC
- CVE-2022-42928: Missing KeepAlive annotations for some BigInt operations may lead to memory corruption
- CVE-2022-45406: Use-after-free of a JavaScript Realm
- CVE-2023-4577: Memory corruption due to interaction of GC and RegEx
- CVE-2023-5171: GC resulted in a use-after-free condition during compilation
Chromium/v8* Issue 939316: Turbofan pode ler um ponteiro de Map fora dos limites ao otimizar Reflect.construct
- Issue 944062: JSCallReducer::ReduceArrayIndexOfIncludes falha ao inserir verificações de Map
- CVE-2019-5831: Processamento incorreto de Map no V8
- Issue 944865: Representação de valor inválida no V8
- CVE-2019-5841: Erro na heurística de inlining
- CVE-2019-5847: Elementos selados/congelados do V8 causam crash
- CVE-2019-5853: Corrupção de memória na verificação de comprimento de regexp
- Issue 992914: Migração de Map não respeita tipos de elemento, levando a confusão de tipos
- CVE-2020-6512: Confusão de tipos no V8
- CVE-2020-16006: Corrupção de memória devido a colisão de hash tratada incorretamente em DescriptorArray
- CVE-2021-37991: Condição de corrida durante compilação JIT concorrente
- Issue 1359937: Desserialização de BigInts pode resultar em valor -0n inválido
- Issue 1377775: Verificação de tipo incorreta ao fazer inlining de Array.prototype.at no Turbofan
- Issue 2323: Ponteiro valstack instável em putprop
- Issue 2320: Estouro de ponteiro no memcmp em string builtin
- CVE-2020-13991: Liberação incorreta de argumentos spread
- Issue 3784: Corrupção de memória devido a enumeração incorreta de propriedades
- CVE-2020-13623: Estouro de pilha através de chaves de propriedade para objetos Proxy
- CVE-2020-13649 (1): Corrupção de memória devido a tratamento de erros em caso de OOM
- CVE-2020-13649 (2): Corrupção de memória devido a tratamento de erros em caso de OOM
- CVE-2020-13622: Corrupção de memória devido a tratamento incorreto de chaves de propriedade para objetos Proxy
- CVE-2020-14163: Corrupção de memória devido a condição de corrida desencadeada pela coleta de lixo ao adicionar pares chave/valor
- Issue 3813: Tratamento de erros incorreto na função SerializeJSONProperty
- Issue 3814: Objeto Proxy inesperado na asserção ecma_op_function_has_instance
- Issue 3836: Corrupção de memória devido a inicialização incorreta de TypedArray
- Issue 3837: Corrupção de memória devido a tratamento incorreto de memória em getOwnPropertyDescriptor
- CVE-2020-1912: Corrupção de memória ao executar funções geradoras internas compiladas de forma preguiçosa
- CVE-2020-1914: Corrupção de bytecode ao processar a instrução SaveGeneratorLong
Aviso legal
Este não é um produto oficialmente suportado pelo Google.