Skip to content

Commit 8fef24a

Browse files
authored
Merge branch 'main' into distributed-database
2 parents 0df6281 + ffe557b commit 8fef24a

11 files changed

Lines changed: 437 additions & 6 deletions

Scripts/SetupPostgres.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ echo "To stop PostgreSQL:"
100100
echo " $COMPOSE_CMD down"
101101
echo ""
102102
echo "To view logs:"
103-
echo " $COMPOSE_CMD logs postgres"
103+
echo " $COMPOSE_CMD logs postgres"

Sources/Fuzzilli/Engines/FuzzEngine.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class FuzzEngine: ComponentBase {
4242
switch execution.outcome {
4343
case .crashed(let termsig):
4444
fuzzer.processCrash(program, withSignal: termsig, withStderr: execution.stderr, withStdout: execution.stdout, origin: .local, withExectime: execution.execTime)
45+
fuzzer.runtimeWeightedMutators.adjustBatchWeight(fuzzer.runtimeWeightedMutators.getLastElements(), 1.1, 0.9)
4546
program.contributors.generatedCrashingSample()
4647

4748
case .succeeded:
@@ -57,8 +58,10 @@ public class FuzzEngine: ComponentBase {
5758

5859
if isInteresting {
5960
program.contributors.generatedInterestingSample()
61+
fuzzer.runtimeWeightedMutators.adjustBatchWeight(fuzzer.runtimeWeightedMutators.getLastElements(), 1.1, 0.9)
6062
} else {
6163
program.contributors.generatedValidSample()
64+
fuzzer.runtimeWeightedMutators.adjustBatchWeight(fuzzer.runtimeWeightedMutators.getLastElements(), 0.9, 1.1)
6265
}
6366

6467
case .failed(_):
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
public class RuntimeHybridEngine: FuzzEngine {
18+
// The number of mutations to perform to a single sample per round
19+
private let numConsecutiveMutations: Int
20+
21+
// The different outcomes of one fuzzing iterations.
22+
private enum CodeGenerationOutcome: String, CaseIterable {
23+
case success = "Success"
24+
case generatedCodeFailed = "Generated code failed"
25+
case generatedCodeTimedOut = "Generated code timed out"
26+
case generatedCodeCrashed = "Generated code crashed"
27+
}
28+
private var outcomeCounts = [CodeGenerationOutcome: Int]()
29+
30+
// Additional statistics about the generated programs.
31+
private var totalInstructionsGenerated = 0
32+
private var programsGenerated = 0
33+
private var percentageOfGuardedOperationsAfterCodeGeneration = MovingAverage(n: 1000)
34+
private var percentageOfGuardedOperationsAfterCodeRefining = MovingAverage(n: 1000)
35+
36+
// We use the FixupMutator to "fix" the generated programs based on runtime information (e.g. remove unneeded try-catch).
37+
private var fixupMutator = FixupMutator(name: "HybridEngineFixupMutator")
38+
39+
public init(numConsecutiveMutations: Int) {
40+
self.numConsecutiveMutations = numConsecutiveMutations
41+
super.init(name: "HybridEngine")
42+
43+
for outcome in CodeGenerationOutcome.allCases {
44+
outcomeCounts[outcome] = 0
45+
}
46+
}
47+
48+
override func initialize() {
49+
if fuzzer.config.logLevel.isAtLeast(.verbose) {
50+
fuzzer.timers.scheduleTask(every: 30 * Minutes) {
51+
guard self.programsGenerated > 0 else { return }
52+
53+
// TODO move into Statistics?
54+
self.logger.verbose("Program Template Statistics:")
55+
let nameMaxLength = self.fuzzer.programTemplates.map({ $0.name.count }).max()!
56+
for template in self.fuzzer.programTemplates {
57+
let name = template.name.rightPadded(toLength: nameMaxLength)
58+
let correctnessRate = Statistics.percentageOrNa(template.correctnessRate, 7)
59+
let interestingSamplesRate = Statistics.percentageOrNa(template.interestingSamplesRate, 7)
60+
let timeoutRate = Statistics.percentageOrNa(template.timeoutRate, 6)
61+
let avgInstructionsAdded = String(format: "%.2f", template.avgNumberOfInstructionsGenerated).leftPadded(toLength: 5)
62+
let samplesGenerated = template.totalSamples
63+
self.logger.verbose(" \(name) : Correctness rate: \(correctnessRate), Interesting sample rate: \(interestingSamplesRate), Timeout rate: \(timeoutRate), Avg. # of instructions generated: \(avgInstructionsAdded), Total # of generated samples: \(samplesGenerated)")
64+
}
65+
66+
let totalOutcomes = self.outcomeCounts.values.reduce(0, +)
67+
self.logger.verbose("Frequencies of code generation outcomes:")
68+
for outcome in CodeGenerationOutcome.allCases {
69+
let count = self.outcomeCounts[outcome]!
70+
let frequency = (Double(count) / Double(totalOutcomes)) * 100.0
71+
self.logger.verbose(" \(outcome.rawValue.rightPadded(toLength: 25)): \(String(format: "%.2f%%", frequency))")
72+
}
73+
74+
self.logger.verbose("Number of generated programs: \(self.programsGenerated)")
75+
self.logger.verbose("Average programs size: \(self.totalInstructionsGenerated / self.programsGenerated)")
76+
self.logger.verbose("Average percentage of guarded operations after code generation: \(String(format: "%.2f%", self.percentageOfGuardedOperationsAfterCodeGeneration.currentValue))%")
77+
self.logger.verbose("Average percentage of guarded operations after code refining: \(String(format: "%.2f%", self.percentageOfGuardedOperationsAfterCodeRefining.currentValue))%")
78+
}
79+
}
80+
}
81+
82+
private func generateTemplateProgram(template: ProgramTemplate) -> Program {
83+
let b = fuzzer.makeBuilder()
84+
b.traceHeader("Generating program based on \(template.name) template")
85+
template.generate(in: b)
86+
let program = b.finalize()
87+
88+
program.contributors.insert(template)
89+
template.addedInstructions(program.size)
90+
return program
91+
}
92+
93+
public override func fuzzOne(_ group: DispatchGroup) {
94+
let template = fuzzer.programTemplates.randomElement()
95+
96+
let generatedProgram = generateTemplateProgram(template: template)
97+
98+
// Update basic codegen statistics.
99+
totalInstructionsGenerated += generatedProgram.size
100+
programsGenerated += 1
101+
percentageOfGuardedOperationsAfterCodeGeneration.add(computePercentageOfGuardedOperations(in: generatedProgram))
102+
103+
// We use a higher timeout for the initial execution as pure code generation should only rarely lead to infinite loops/recursion.
104+
// On the other hand, the generated program may contain slow operations (e.g. try-catch guards) that the subsequent fixup may remove.
105+
let outcome = execute(generatedProgram, withTimeout: fuzzer.config.timeout * 2)
106+
switch outcome {
107+
case .succeeded:
108+
recordOutcome(.success)
109+
case .failed:
110+
return recordOutcome(.generatedCodeFailed)
111+
case .timedOut:
112+
return recordOutcome(.generatedCodeTimedOut)
113+
case .crashed:
114+
return recordOutcome(.generatedCodeCrashed)
115+
}
116+
117+
// Now perform one round of fixup to improve the generated program based on runtime information and in particular remove all try-catch guards that are not needed.
118+
// For example, at runtime we'll know the exact type of variables, including object methods and properties, which we do not necessarily know statically during code generation.
119+
// As such, it is much easier to select a "good" method/property to access at runtime than it is during static code generation. Further, it is trivial to determine which
120+
// operations raise an exception at runtime, but hard to determine that statically at code generation time. So we can be overly conservative and wrap many operations in
121+
// try-catch (i.e. "guard" them), then remove the unnecessary guards after code generation based on runtime information. This is what fixup achieves.
122+
let refinedProgram: Program
123+
if let result = fixupMutator.mutate(generatedProgram, for: fuzzer) {
124+
refinedProgram = result
125+
percentageOfGuardedOperationsAfterCodeRefining.add(computePercentageOfGuardedOperations(in: refinedProgram))
126+
} else {
127+
// Fixup is expected to fail sometimes, for example if there is nothing to fix.
128+
refinedProgram = generatedProgram
129+
}
130+
131+
// Now mutate the program a number of times.
132+
// We do this for example because pure code generation will often not generate "weird" code (e.g. weird inputs to operations, infinite loops, very large arrays, odd-looking object/class literals, etc.), but mutators are pretty good at that.
133+
// Further, some mutators have access to runtime information (e.g. Probe and Explore mutator) which the static code generation lacks.
134+
var parent = refinedProgram
135+
for _ in 0..<numConsecutiveMutations {
136+
// TODO: factor out code shared with the MutationEngine?
137+
var mutator = fuzzer.runtimeWeightedMutators.weightedElement()
138+
let maxAttempts = 10
139+
var mutatedProgram: Program? = nil
140+
for _ in 0..<maxAttempts {
141+
if let result = mutator.mutate(parent, for: fuzzer) {
142+
// Success!
143+
result.contributors.formUnion(parent.contributors)
144+
mutator.addedInstructions(result.size - parent.size)
145+
mutatedProgram = result
146+
break
147+
} else {
148+
// Try a different mutator.
149+
mutator.failedToGenerate()
150+
fuzzer.runtimeWeightedMutators.popLastElement()
151+
mutator = fuzzer.runtimeWeightedMutators.weightedElement()
152+
}
153+
}
154+
155+
guard let program = mutatedProgram else {
156+
logger.warning("Could not mutate sample, giving up. Sample:\n\(FuzzILLifter().lift(parent))")
157+
continue
158+
}
159+
160+
assert(program !== parent)
161+
let outcome = execute(program)
162+
163+
// Mutate the program further if it succeeded.
164+
if .succeeded == outcome {
165+
parent = program
166+
}
167+
}
168+
}
169+
170+
private func recordOutcome(_ outcome: CodeGenerationOutcome) {
171+
outcomeCounts[outcome]! += 1
172+
}
173+
174+
private func computePercentageOfGuardedOperations(in program: Program) -> Double {
175+
let numGuardedOperations = Double(program.code.filter({ $0.isGuarded }).count)
176+
// We also count try-catch blocks as guards for the purpose of these statistics, and we count them as 3 instructions
177+
// as they at least need the BeginTry and EndTryCatchFinally, plus either a BeginCatch or BeginFinally.
178+
let numTryCatchBlocks = Double(program.code.filter({ $0.op is BeginTry }).count)
179+
return ((numGuardedOperations + numTryCatchBlocks * 3) / Double(program.size)) * 100.0
180+
}
181+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
/// The core fuzzer responsible for generating and executing programs.
18+
public class RuntimeMutationEngine: FuzzEngine {
19+
// The number of consecutive mutations to apply to a sample.
20+
private let numConsecutiveMutations: Int
21+
22+
public init(numConsecutiveMutations: Int) {
23+
self.numConsecutiveMutations = numConsecutiveMutations
24+
super.init(name: "MutationEngine")
25+
}
26+
27+
/// Perform one round of fuzzing.
28+
///
29+
/// High-level fuzzing algorithm:
30+
///
31+
/// let parent = pickSampleFromCorpus()
32+
/// repeat N times:
33+
/// let current = mutate(parent)
34+
/// execute(current)
35+
/// if current produced crashed:
36+
/// output current
37+
/// elif current resulted in a runtime exception or a time out:
38+
/// // do nothing
39+
/// elif current produced new, interesting behaviour:
40+
/// minimize and add to corpus
41+
/// else
42+
/// parent = current
43+
///
44+
///
45+
/// This ensures that samples will be mutated multiple times as long
46+
/// as the intermediate results do not cause a runtime exception.
47+
public override func fuzzOne(_ group: DispatchGroup) {
48+
var parent = fuzzer.corpus.randomElementForMutating()
49+
parent = prepareForMutating(parent)
50+
for _ in 0..<numConsecutiveMutations {
51+
// TODO: factor out code shared with the HybridEngine?
52+
var mutator = fuzzer.runtimeWeightedMutators.weightedElement()
53+
let maxAttempts = 10
54+
var mutatedProgram: Program? = nil
55+
for _ in 0..<maxAttempts {
56+
if let result = mutator.mutate(parent, for: fuzzer) {
57+
// Success!
58+
result.contributors.formUnion(parent.contributors)
59+
mutator.addedInstructions(result.size - parent.size)
60+
mutatedProgram = result
61+
break
62+
} else {
63+
// Try a different mutator.
64+
mutator.failedToGenerate()
65+
fuzzer.runtimeWeightedMutators.popLastElement()
66+
mutator = fuzzer.runtimeWeightedMutators.weightedElement()
67+
}
68+
}
69+
70+
guard let program = mutatedProgram else {
71+
logger.warning("Could not mutate sample, giving up. Sample:\n\(FuzzILLifter().lift(parent))")
72+
continue
73+
}
74+
75+
assert(program !== parent)
76+
let outcome = execute(program)
77+
78+
// Mutate the program further if it succeeded.
79+
if .succeeded == outcome {
80+
parent = program
81+
}
82+
}
83+
}
84+
85+
/// Pre-processing of programs to facilitate mutations on them.
86+
private func prepareForMutating(_ program: Program) -> Program {
87+
let b = fuzzer.makeBuilder()
88+
b.buildPrefix()
89+
b.append(program)
90+
return b.finalize()
91+
}
92+
}

Sources/Fuzzilli/Engines/RuntimeMutationWeighting.swift

Whitespace-only changes.

Sources/Fuzzilli/Fuzzer.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public class Fuzzer {
5151
/// The mutators used by the engine.
5252
public let mutators: WeightedList<Mutator>
5353

54+
/// The mutators used by the engine when dynamically weighted during runtime
55+
public let runtimeWeightedMutators: RuntimeWeightedList<Mutator>
56+
5457
/// The evaluator to score generated programs.
5558
public let evaluator: ProgramEvaluator
5659

@@ -160,6 +163,7 @@ public class Fuzzer {
160163
public init(
161164
configuration: Configuration, scriptRunner: ScriptRunner, engine: FuzzEngine, mutators: WeightedList<Mutator>,
162165
codeGenerators: WeightedList<CodeGenerator>, programTemplates: WeightedList<ProgramTemplate>, evaluator: ProgramEvaluator,
166+
runtimeWeightedMutators: RuntimeWeightedList<Mutator>,
163167
environment: JavaScriptEnvironment, lifter: Lifter, corpus: Corpus, minimizer: Minimizer, queue: DispatchQueue? = nil
164168
) {
165169
let uniqueId = UUID()
@@ -171,6 +175,7 @@ public class Fuzzer {
171175
self.timers = Timers(queue: self.queue)
172176
self.engine = engine
173177
self.mutators = mutators
178+
self.runtimeWeightedMutators = runtimeWeightedMutators
174179
self.codeGenerators = codeGenerators
175180

176181
self.programTemplates = programTemplates
@@ -910,6 +915,7 @@ public class Fuzzer {
910915

911916
// Perform the next iteration as soon as all tasks related to the current iteration are finished.
912917
fuzzGroup.notify(queue: queue) {
918+
self.runtimeWeightedMutators.flushLastElements()
913919
self.fuzzOne()
914920
}
915921
}

Sources/Fuzzilli/Util/MockFuzzer.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public func makeMockFuzzer(config maybeConfiguration: Configuration? = nil, engi
9696
(CombineMutator(), 1),
9797
])
9898

99+
let runtimeMutators = RuntimeWeightedList<Mutator>([
100+
(CodeGenMutator(), 1),
101+
(OperationMutator(), 1),
102+
(InputMutator(typeAwareness: .loose), 1),
103+
(CombineMutator(), 1)
104+
])
105+
99106
let engine = maybeEngine ?? MutationEngine(numConsecutiveMutations: 5)
100107

101108
// The evaluator to score produced samples.
@@ -133,6 +140,7 @@ public func makeMockFuzzer(config maybeConfiguration: Configuration? = nil, engi
133140
codeGenerators: codeGenerators,
134141
programTemplates: programTemplates,
135142
evaluator: evaluator,
143+
runtimeWeightedMutators: runtimeMutators,
136144
environment: environment,
137145
lifter: lifter,
138146
corpus: corpus,

0 commit comments

Comments
 (0)