Skip to content

Commit 7f04c16

Browse files
Danylo MocherniukV8-internal LUCI CQ
authored andcommitted
[dumpling] Implement differential fuzzing
Bug: 441467877 Change-Id: I7278380605e40ca79b4dc889cb8b6734aa7c4327 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8908076 Reviewed-by: Matthias Liedtke <mliedtke@google.com> Commit-Queue: Danylo Mocherniuk <mdanylo@google.com>
1 parent 00086f6 commit 7f04c16

26 files changed

Lines changed: 423 additions & 14 deletions

Sources/Fuzzilli/Base/Contributor.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class Contributor: Hashable {
2828
private var timedOutSamples = 0
2929
// Number of crashing programs produced.
3030
private var crashingSamples = 0
31+
// The number of programs resulting in valid differential produced.
32+
private var differentialSamples = 0
3133
// The number of times this contributor has been invoked
3234
private(set) var invocationCount = 0
3335
// The number of times this contributor has actually emitted more than 0 instructions
@@ -46,6 +48,10 @@ public class Contributor: Hashable {
4648
validSamples += 1
4749
}
4850

51+
func generatedDifferentialSample() {
52+
differentialSamples += 1
53+
}
54+
4955
func generatedInterestingSample() {
5056
interestingSamples += 1
5157
}
@@ -82,8 +88,12 @@ public class Contributor: Hashable {
8288
return crashingSamples
8389
}
8490

91+
public var differentialsFound: Int {
92+
return differentialSamples
93+
}
94+
8595
public var totalSamples: Int {
86-
return validSamples + interestingSamples + invalidSamples + timedOutSamples + crashingSamples
96+
return validSamples + interestingSamples + invalidSamples + timedOutSamples + crashingSamples + differentialSamples
8797
}
8898

8999
// If this is low, that means the CodeGenerator has dynamic requirements that are not met most of the time.
@@ -151,4 +161,8 @@ extension Contributors {
151161
public func generatedTimeOutSample() {
152162
forEach { $0.generatedTimeOutSample() }
153163
}
164+
165+
public func generatedDifferentialSample() {
166+
forEach { $0.generatedDifferentialSample() }
167+
}
154168
}

Sources/Fuzzilli/Base/Events.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public class Events {
5555
/// Signals that a crashing program has been found. Dispatched after the crashing program has been minimized.
5656
public let CrashFound = Event<(program: Program, behaviour: CrashBehaviour, isUnique: Bool, origin: ProgramOrigin)>()
5757

58+
/// Signals that a differential program was found. Dispatched after the differential program has been minimized.
59+
public let DifferentialFound = Event<(program: Program, behaviour: CrashBehaviour, isUnique: Bool, origin: ProgramOrigin)>()
60+
5861
/// Signals that a program causing a timeout has been found.
5962
public let TimeOutFound = Event<Program>()
6063

@@ -140,4 +143,15 @@ public enum ExecutionPurpose {
140143
case runtimeAssistedMutation
141144
/// Any other reason.
142145
case other
146+
147+
// This variable is added because we currently don't want to differentially execute
148+
// a sample if the purpose is unknown (`.other`), `.startup` or `.runtimeAssistedMutation`.
149+
public var supportsDifferentialRun: Bool {
150+
switch self {
151+
case .fuzzing, .checkForDeterministicBehavior, .programImport, .minimization:
152+
return true
153+
case .startup, .runtimeAssistedMutation, .other:
154+
return false
155+
}
156+
}
143157
}

Sources/Fuzzilli/Configuration.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,36 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// TODO(mdanylo): this should be part of the protocol between Fuzzilli and V8.
16+
public struct DifferentialConfig {
17+
public let dumpFilenamePattern: String
18+
19+
public init(dumpFilenamePattern: String) {
20+
self.dumpFilenamePattern = dumpFilenamePattern
21+
}
22+
23+
public func getDumpFilename(isOptimized: Bool) -> String {
24+
let subDirectory = isOptimized ? "optimizedDump" : "unoptimizedDump"
25+
return String(format: dumpFilenamePattern, subDirectory)
26+
}
27+
28+
public func getDumpFilenameParameter(isOptimized: Bool) -> String {
29+
return "--dump-out-filename=\(getDumpFilename(isOptimized: isOptimized))"
30+
}
31+
32+
public static func create(for instanceId: Int, storagePath: String) -> DifferentialConfig {
33+
// TODO(mdanylo): it would be cool to add random hash to the filename if we store dumps
34+
// in the filesystem for debugging.
35+
let fileName = "output_dump_\(instanceId).txt"
36+
let pattern = "\(storagePath)/%@/\(fileName)"
37+
return DifferentialConfig(dumpFilenamePattern: pattern)
38+
}
39+
}
40+
1541
public struct Configuration {
42+
/// The config specific to differential fuzzing
43+
public let diffConfig: DifferentialConfig?
44+
1645
/// The commandline arguments used by this instance.
1746
public let arguments: [String]
1847

@@ -91,7 +120,9 @@ public struct Configuration {
91120
tag: String? = nil,
92121
isWasmEnabled: Bool = false,
93122
storagePath: String? = nil,
94-
forDifferentialFuzzing: Bool = false) {
123+
forDifferentialFuzzing: Bool = false,
124+
instanceId: Int = -1,
125+
dumplingEnabled: Bool = false) {
95126
self.arguments = arguments
96127
self.timeout = timeout
97128
self.logLevel = logLevel
@@ -106,6 +137,11 @@ public struct Configuration {
106137
self.isWasmEnabled = isWasmEnabled
107138
self.storagePath = storagePath
108139
self.forDifferentialFuzzing = forDifferentialFuzzing
140+
self.diffConfig = dumplingEnabled ? DifferentialConfig.create(for: instanceId, storagePath: storagePath!) : nil
141+
}
142+
143+
public func getInstanceSpecificArguments(forReferenceRunner: Bool) -> [String] {
144+
return diffConfig.map { [$0.getDumpFilenameParameter(isOptimized: !forReferenceRunner)] } ?? []
109145
}
110146
}
111147

Sources/Fuzzilli/Engines/FuzzEngine.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public class FuzzEngine: ComponentBase {
4444
fuzzer.processCrash(program, withSignal: termsig, withStderr: execution.stderr, withStdout: execution.stdout, origin: .local, withExectime: execution.execTime)
4545
program.contributors.generatedCrashingSample()
4646

47+
case .differential:
48+
fuzzer.processDifferential(program, withStderr: execution.stderr, withStdout: execution.stdout, origin: .local)
49+
program.contributors.generatedDifferentialSample()
50+
4751
case .succeeded:
4852
fuzzer.dispatchEvent(fuzzer.events.ValidProgramFound, data: program)
4953
var isInteresting = false

Sources/Fuzzilli/Engines/HybridEngine.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class HybridEngine: FuzzEngine {
2121
// The different outcomes of one fuzzing iterations.
2222
private enum CodeGenerationOutcome: String, CaseIterable {
2323
case success = "Success"
24+
case generatedCodeDifferential = "Generated code differential"
2425
case generatedCodeFailed = "Generated code failed"
2526
case generatedCodeTimedOut = "Generated code timed out"
2627
case generatedCodeCrashed = "Generated code crashed"
@@ -112,6 +113,8 @@ public class HybridEngine: FuzzEngine {
112113
return recordOutcome(.generatedCodeTimedOut)
113114
case .crashed:
114115
return recordOutcome(.generatedCodeCrashed)
116+
case .differential:
117+
return recordOutcome(.generatedCodeDifferential)
115118
}
116119

117120
// 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.

Sources/Fuzzilli/Evaluation/ProgramCoverageEvaluator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ public class ProgramCoverageEvaluator: ComponentBase, ProgramEvaluator {
192192
return false
193193
}
194194

195-
if execution.outcome.isCrash() {
196-
// For crashes, we don't care about the edges that were triggered, just about the outcome itself.
195+
if execution.outcome.isCrash() || execution.outcome.isDifferential() {
196+
// For crashes and differentials, we don't care about the edges that were triggered, just about the outcome itself.
197197
return true
198198
}
199199

Sources/Fuzzilli/Execution/Execution.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public enum ExecutionOutcome: CustomStringConvertible, Equatable, Hashable {
2020
case failed(Int)
2121
case succeeded
2222
case timedOut
23+
// This outcome is added to support native differential fuzzing.
24+
// It should get very similar treatment to crashed -> if the run resulted
25+
// in a differential, most likely there's a bug.
26+
// Please note that this feature is unstable yet, so the statement above
27+
// might not always be the case.
28+
case differential
2329

2430
public var description: String {
2531
switch self {
@@ -31,6 +37,8 @@ public enum ExecutionOutcome: CustomStringConvertible, Equatable, Hashable {
3137
return "Succeeded"
3238
case .timedOut:
3339
return "TimedOut"
40+
case .differential:
41+
return "Differential"
3442
}
3543
}
3644

@@ -49,6 +57,14 @@ public enum ExecutionOutcome: CustomStringConvertible, Equatable, Hashable {
4957
return false
5058
}
5159
}
60+
61+
public func isDifferential() -> Bool {
62+
if case .differential = self {
63+
return true
64+
} else {
65+
return false
66+
}
67+
}
5268
}
5369

5470
/// The result of executing a program.
@@ -59,3 +75,54 @@ public protocol Execution {
5975
var fuzzout: String { get }
6076
var execTime: TimeInterval { get }
6177
}
78+
79+
/// Struct to capture result of exection in differential mode
80+
struct DiffExecution: Execution {
81+
let outcome: ExecutionOutcome
82+
let execTime: TimeInterval
83+
let stdout: String
84+
let stderr: String
85+
let fuzzout: String
86+
87+
private init(
88+
outcome: ExecutionOutcome,
89+
execTime: TimeInterval,
90+
stdout: String,
91+
stderr: String,
92+
fuzzout: String
93+
) {
94+
self.outcome = outcome
95+
self.execTime = execTime
96+
self.stdout = stdout
97+
self.stderr = stderr
98+
self.fuzzout = fuzzout
99+
}
100+
101+
// TODO(mdanylo): we shouldn't pass dump outputs as a separate parameter,
102+
// instead we should rather make them a part of a REPRL protocol between Fuzzilli and V8.
103+
static func diff(optExec: Execution, unoptExec: Execution,
104+
optDumpOut: String, unoptDumpOut: String) -> Execution {
105+
106+
assert(optExec.outcome == .succeeded && unoptExec.outcome == .succeeded)
107+
108+
func formatDiff(label: String, optData: String, unoptData: String) -> String {
109+
return """
110+
=== OPT \(label) ===
111+
\(optData)
112+
113+
=== UNOPT \(label) ===
114+
\(unoptData)
115+
"""
116+
}
117+
118+
let relateOutcome = DiffOracle.relate(optDumpOut, with: unoptDumpOut)
119+
120+
return DiffExecution(
121+
outcome: relateOutcome ? .succeeded : .differential,
122+
execTime: optExec.execTime,
123+
stdout: formatDiff(label: "STDOUT", optData: optExec.stdout, unoptData: unoptExec.stdout),
124+
stderr: formatDiff(label: "STDERR", optData: optExec.stderr, unoptData: unoptExec.stderr),
125+
fuzzout: formatDiff(label: "FUZZOUT", optData: optExec.fuzzout, unoptData: unoptExec.fuzzout)
126+
)
127+
}
128+
}

0 commit comments

Comments
 (0)