diff --git a/Sources/Fuzzilli/Engines/FuzzEngine.swift b/Sources/Fuzzilli/Engines/FuzzEngine.swift index 7f5620a8c..1b5a61044 100644 --- a/Sources/Fuzzilli/Engines/FuzzEngine.swift +++ b/Sources/Fuzzilli/Engines/FuzzEngine.swift @@ -14,13 +14,27 @@ import Foundation -public protocol FuzzEngine: ComponentBase { +public class FuzzEngine: ComponentBase { + private var postProcessor: FuzzingPostProcessor? = nil + + override init(name: String) { + super.init(name: name) + } + + // Install a post-processor that is executed for every generated program and can modify it (by returning a different program). + public func registerPostProcessor(_ postProcessor: FuzzingPostProcessor) { + assert(self.postProcessor == nil) + self.postProcessor = postProcessor + } + // Performs a single round of fuzzing using the engine. - func fuzzOne(_ group: DispatchGroup) -} + public func fuzzOne(_ group: DispatchGroup) { + fatalError("Must be implemented by child classes") + } + + final func execute(_ program: Program, withTimeout timeout: UInt32? = nil) -> ExecutionOutcome { + let program = postProcessor?.process(program, for: fuzzer) ?? program -extension FuzzEngine { - public func execute(_ program: Program, withTimeout timeout: UInt32? = nil) -> ExecutionOutcome { fuzzer.dispatchEvent(fuzzer.events.ProgramGenerated, data: program) let execution = fuzzer.execute(program, withTimeout: timeout, purpose: .fuzzing) @@ -67,7 +81,7 @@ extension FuzzEngine { return execution.outcome } - private func ensureDeterministicExecutionOutcomeForDiagnostic(of program: Program) { + private final func ensureDeterministicExecutionOutcomeForDiagnostic(of program: Program) { let execution1 = fuzzer.execute(program, purpose: .other) let stdout1 = execution1.stdout, stderr1 = execution1.stderr let execution2 = fuzzer.execute(program, purpose: .other) diff --git a/Sources/Fuzzilli/Engines/FuzzingPostProcessor.swift b/Sources/Fuzzilli/Engines/FuzzingPostProcessor.swift new file mode 100644 index 000000000..e0c61f27f --- /dev/null +++ b/Sources/Fuzzilli/Engines/FuzzingPostProcessor.swift @@ -0,0 +1,20 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +// A post-processor can be used to modify samples generated during fuzzing before executing them. +public protocol FuzzingPostProcessor { + func process(_ program: Program, for fuzzer: Fuzzer) -> Program +} diff --git a/Sources/Fuzzilli/Engines/GenerativeEngine.swift b/Sources/Fuzzilli/Engines/GenerativeEngine.swift index 3886a5f00..f2a5115ee 100644 --- a/Sources/Fuzzilli/Engines/GenerativeEngine.swift +++ b/Sources/Fuzzilli/Engines/GenerativeEngine.swift @@ -15,7 +15,7 @@ import Foundation /// Purely generative fuzzing engine, mostly used for initial corpus generation when starting without an existing corpus. -public class GenerativeEngine: ComponentBase, FuzzEngine { +public class GenerativeEngine: FuzzEngine { /// Approximate number of instructions to generate in additional to any prefix code. private let numInstructionsToGenerate = 10 @@ -24,7 +24,7 @@ public class GenerativeEngine: ComponentBase, FuzzEngine { } /// Perform one round of fuzzing: simply generate a new program and execute it - public func fuzzOne(_ group: DispatchGroup) { + public override func fuzzOne(_ group: DispatchGroup) { let b = fuzzer.makeBuilder() // Start by building a prefix that creates some variables (of known types) that the following CodeGenerators can then make use of. diff --git a/Sources/Fuzzilli/Engines/HybridEngine.swift b/Sources/Fuzzilli/Engines/HybridEngine.swift index d735680d4..f25ab27ef 100644 --- a/Sources/Fuzzilli/Engines/HybridEngine.swift +++ b/Sources/Fuzzilli/Engines/HybridEngine.swift @@ -14,7 +14,7 @@ import Foundation -public class HybridEngine: ComponentBase, FuzzEngine { +public class HybridEngine: FuzzEngine { // The number of mutations to perform to a single sample per round private let numConsecutiveMutations: Int @@ -90,7 +90,7 @@ public class HybridEngine: ComponentBase, FuzzEngine { return program } - public func fuzzOne(_ group: DispatchGroup) { + public override func fuzzOne(_ group: DispatchGroup) { let template = fuzzer.programTemplates.randomElement() let generatedProgram = generateTemplateProgram(template: template) diff --git a/Sources/Fuzzilli/Engines/MultiEngine.swift b/Sources/Fuzzilli/Engines/MultiEngine.swift index b96cead9a..2eab72945 100644 --- a/Sources/Fuzzilli/Engines/MultiEngine.swift +++ b/Sources/Fuzzilli/Engines/MultiEngine.swift @@ -17,7 +17,7 @@ import Foundation /// Wraps multiple engines into one, which can be initialized given a /// WeightedList. This can then switch engines to use, or use a more /// complicated heuristic. -public class MultiEngine: ComponentBase, FuzzEngine { +public class MultiEngine: FuzzEngine { let engines: WeightedList /// The current active engine. @@ -43,7 +43,7 @@ public class MultiEngine: ComponentBase, FuzzEngine { } } - public func fuzzOne(_ group: DispatchGroup) { + public override func fuzzOne(_ group: DispatchGroup) { activeEngine.fuzzOne(group) currentIteration += 1 if currentIteration % iterationsPerEngine == 0 { diff --git a/Sources/Fuzzilli/Engines/MutationEngine.swift b/Sources/Fuzzilli/Engines/MutationEngine.swift index fc0321ae0..a02176bee 100644 --- a/Sources/Fuzzilli/Engines/MutationEngine.swift +++ b/Sources/Fuzzilli/Engines/MutationEngine.swift @@ -15,7 +15,7 @@ import Foundation /// The core fuzzer responsible for generating and executing programs. -public class MutationEngine: ComponentBase, FuzzEngine { +public class MutationEngine: FuzzEngine { // The number of consecutive mutations to apply to a sample. private let numConsecutiveMutations: Int @@ -44,7 +44,7 @@ public class MutationEngine: ComponentBase, FuzzEngine { /// /// This ensures that samples will be mutated multiple times as long /// as the intermediate results do not cause a runtime exception. - public func fuzzOne(_ group: DispatchGroup) { + public override func fuzzOne(_ group: DispatchGroup) { var parent = fuzzer.corpus.randomElementForMutating() parent = prepareForMutating(parent) for _ in 0.. Fuzzer { +public func makeMockFuzzer(config maybeConfiguration: Configuration? = nil, engine maybeEngine: FuzzEngine? = nil, runner maybeRunner: ScriptRunner? = nil, environment maybeEnvironment: Environment? = nil, evaluator maybeEvaluator: ProgramEvaluator? = nil, corpus maybeCorpus: Corpus? = nil) -> Fuzzer { dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) // The configuration of this fuzzer. diff --git a/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift b/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift index bca397b1a..8d38b9be0 100644 --- a/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift +++ b/Sources/FuzzilliCli/Profiles/DuktapeProfile.swift @@ -52,5 +52,7 @@ let duktapeProfile = Profile( "Duktape.compact" : .function([.object()] => .undefined), "placeholder" : .function([] => .undefined), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/JSCProfile.swift b/Sources/FuzzilliCli/Profiles/JSCProfile.swift index 4e2cbf311..ee6a2f372 100644 --- a/Sources/FuzzilliCli/Profiles/JSCProfile.swift +++ b/Sources/FuzzilliCli/Profiles/JSCProfile.swift @@ -113,5 +113,7 @@ let jscProfile = Profile( "fiatInt52" : .function([.number] => .number), "forceGCSlowPaths" : .function([] => .anything), "ensureArrayStorage" : .function([] => .anything), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift b/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift index 463221f92..3a41c6483 100644 --- a/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift +++ b/Sources/FuzzilliCli/Profiles/JerryscriptProfile.swift @@ -48,5 +48,7 @@ let jerryscriptProfile = Profile( "print" : .function([] => .undefined), "resourceName" : .function([] => .undefined), "placeholder" : .function([] => .undefined), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/Profile.swift b/Sources/FuzzilliCli/Profiles/Profile.swift index 6c1bfc328..105f1541f 100644 --- a/Sources/FuzzilliCli/Profiles/Profile.swift +++ b/Sources/FuzzilliCli/Profiles/Profile.swift @@ -35,6 +35,9 @@ struct Profile { let disabledMutators: [String] let additionalBuiltins: [String: JSType] + + // An optional post-processor that is executed for every sample generated for fuzzing and can modify it. + let optionalPostProcessor: FuzzingPostProcessor? } let profiles = [ diff --git a/Sources/FuzzilliCli/Profiles/QjsProfile.swift b/Sources/FuzzilliCli/Profiles/QjsProfile.swift index cfd32405f..90bcbf3d1 100644 --- a/Sources/FuzzilliCli/Profiles/QjsProfile.swift +++ b/Sources/FuzzilliCli/Profiles/QjsProfile.swift @@ -45,5 +45,7 @@ let qjsProfile = Profile( additionalBuiltins: [ "placeholder" : .function([] => .undefined) - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/QtjsProfile.swift b/Sources/FuzzilliCli/Profiles/QtjsProfile.swift index cb90d3aa1..75955164c 100644 --- a/Sources/FuzzilliCli/Profiles/QtjsProfile.swift +++ b/Sources/FuzzilliCli/Profiles/QtjsProfile.swift @@ -57,4 +57,7 @@ let qtjsProfile = Profile( additionalBuiltins: [ "gc" : .function([] => .undefined), - ]) + ], + + optionalPostProcessor: nil +) diff --git a/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift b/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift index 61e03eeb2..081cbd39b 100644 --- a/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift +++ b/Sources/FuzzilliCli/Profiles/SpidermonkeyProfile.swift @@ -104,5 +104,7 @@ let spidermonkeyProfile = Profile( "drainJobQueue" : .function([] => .undefined), "bailout" : .function([] => .undefined), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/V8Profile.swift b/Sources/FuzzilliCli/Profiles/V8Profile.swift index 7fd13d255..5d9ea62bb 100644 --- a/Sources/FuzzilliCli/Profiles/V8Profile.swift +++ b/Sources/FuzzilliCli/Profiles/V8Profile.swift @@ -568,5 +568,7 @@ let v8Profile = Profile( "gc" : .function([] => (.undefined | .jsPromise)), "d8" : .object(), "Worker" : .constructor([.anything, .object()] => .object(withMethods: ["postMessage","getMessage"])), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/Profiles/XSProfile.swift b/Sources/FuzzilliCli/Profiles/XSProfile.swift index 3b5fa5c6e..422ecc42e 100644 --- a/Sources/FuzzilliCli/Profiles/XSProfile.swift +++ b/Sources/FuzzilliCli/Profiles/XSProfile.swift @@ -48,5 +48,7 @@ let xsProfile = Profile( "gc" : .function([] => .undefined), "print" : .function([.string] => .undefined), "placeholder" : .function([] => .undefined), - ] + ], + + optionalPostProcessor: nil ) diff --git a/Sources/FuzzilliCli/main.swift b/Sources/FuzzilliCli/main.swift index a2dd98794..98a9787ad 100644 --- a/Sources/FuzzilliCli/main.swift +++ b/Sources/FuzzilliCli/main.swift @@ -421,6 +421,11 @@ func makeFuzzer(with configuration: Configuration) -> Fuzzer { engine = MutationEngine(numConsecutiveMutations: consecutiveMutations) } + // Add a post-processor if the profile defines one. + if let postProcessor = profile.optionalPostProcessor { + engine.registerPostProcessor(postProcessor) + } + // Program templates to use. var programTemplates = profile.additionalProgramTemplates for template in ProgramTemplates {