Skip to content

Commit 3977cfa

Browse files
authored
Merge pull request #41 from VRIG-RITSEC/rebase-downstream-main
Rebase downstream main
2 parents 8f58cd4 + 5fd1e5a commit 3977cfa

46 files changed

Lines changed: 2076 additions & 1536 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/swift.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ jobs:
2929
- uses: actions/setup-node@v4
3030
with:
3131
node-version: 25-nightly
32-
32+
- name: Swift Version
33+
run: swift --version
3334
# Is it failing to run tests b/c we're overriding
3435
# what swift binary to use?
3536
- name: Setup Swift for Ubuntu
@@ -43,6 +44,13 @@ jobs:
4344
- uses: actions/checkout@v2
4445
- name: Build
4546
run: swift build -c ${{ matrix.kind }} -v
47+
- name: Install protobuf
48+
# Install protoc so the presubmit can also validate the generated *.pb.swift files.
49+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.kind == 'debug' }}
50+
run: sudo apt install -y protobuf-compiler
51+
- name: Run presubmit checks
52+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.kind == 'debug' }}
53+
run: python3 Tools/presubmit.py
4654
- name: Run tests with Node.js
4755
run: swift test -c ${{ matrix.kind }} -v
4856
- name: Install jsvu

OWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
cffsmith@google.com
2+
machenbach@google.com
3+
mdanylo@google.com
24
mliedtke@google.com
5+
pawkra@google.com
36
saelo@google.com
7+
tacet@google.com
48

59
per-file WHITESPACE=*

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let package = Package(
2525
.library(name: "Fuzzilli",targets: ["Fuzzilli"]),
2626
],
2727
dependencies: [
28-
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"),
28+
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.32.0"),
2929
.package(
3030
url: "https://github.com/apple/swift-collections.git",
3131
.upToNextMinor(from: "1.2.0")

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
A (coverage-)guided fuzzer for dynamic language interpreters based on a custom intermediate language ("FuzzIL") which can be mutated and translated to JavaScript.
44

5-
Fuzzilli is developed and maintained by:
6-
- Samuel Groß, <saelo@google.com>
7-
- Carl Smith, <cffsmith@google.com>
8-
95
## Usage
106

117
The basic steps to use this fuzzer are:

Sources/FuzzILTool/main.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func loadProgramOrExit(from path: String) -> Program {
9595

9696
let args = Arguments.parse(from: CommandLine.arguments)
9797

98-
if args["-h"] != nil || args["--help"] != nil || args.numPositionalArguments != 1 || args.numOptionalArguments > 2 {
98+
if args["-h"] != nil || args["--help"] != nil || args.numPositionalArguments != 1 {
9999
print("""
100100
Usage:
101101
\(args.programName) options path
@@ -108,6 +108,7 @@ if args["-h"] != nil || args["--help"] != nil || args.numPositionalArguments !=
108108
--dumpProgram : Dumps the internal representation of the program stored in the given protobuf file
109109
--checkCorpus : Attempts to load all .fzil files in a directory and checks if they are statically valid
110110
--compile : Compile the given JavaScript program to a FuzzIL program. Requires node.js
111+
--outputPathJS : If given, --compile will write the lifted JS file to the given path after compilation.
111112
--generate : Generate a random program using Fuzzilli's code generators and save it to the specified path.
112113
--forDifferentialFuzzing : Enable additional features for better support of external differential fuzzing.
113114
""")
@@ -186,17 +187,27 @@ else if args.has("--compile") {
186187
exit(-1)
187188
}
188189

189-
print(fuzzILLifter.lift(program))
190-
print()
191-
print(jsLifter.lift(program))
190+
if let js_path = args["--outputPathJS"] {
191+
let content = jsLifter.lift(program)
192+
do {
193+
try content.write(to: URL(fileURLWithPath: js_path), atomically: false, encoding: String.Encoding.utf8)
194+
} catch {
195+
print("Failed to write file \(js_path): \(error)")
196+
exit(-1)
197+
}
198+
} else {
199+
print(fuzzILLifter.lift(program))
200+
print()
201+
print(jsLifter.lift(program))
192202

193-
do {
194-
let outputPath = URL(fileURLWithPath: path).deletingPathExtension().appendingPathExtension("fzil")
195-
try program.asProtobuf().serializedData().write(to: outputPath)
196-
print("FuzzIL program written to \(outputPath.relativePath)")
197-
} catch {
198-
print("Failed to store output program to disk: \(error)")
199-
exit(-1)
203+
do {
204+
let outputPath = URL(fileURLWithPath: path).deletingPathExtension().appendingPathExtension("fzil")
205+
try program.asProtobuf().serializedData().write(to: outputPath)
206+
print("FuzzIL program written to \(outputPath.relativePath)")
207+
} catch {
208+
print("Failed to store output program to disk: \(error)")
209+
exit(-1)
210+
}
200211
}
201212
}
202213

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,38 @@ import Foundation
1919
/// This provides methods for constructing and appending random
2020
/// instances of the different kinds of operations in a program.
2121
public class ProgramBuilder {
22+
23+
/// Runtime data used by code generators to share data between different stubs inside the same
24+
/// code generator. It is strictly required that all pushed values are also popped again in the
25+
/// same code generator.
26+
public class GeneratorRuntimeData {
27+
private var data = [String : Stack<Variable>]()
28+
29+
public func push(_ key: String, _ value: Variable) {
30+
data[key, default: .init()].push(value)
31+
}
32+
33+
public func pop(_ key: String) -> Variable {
34+
assert(data[key] != nil)
35+
return data[key]!.pop()
36+
}
37+
38+
// Fetch the most recent value for this key and push it back.
39+
public func popAndPush(_ key: String) -> Variable {
40+
assert(data[key] != nil)
41+
return data[key]!.top
42+
}
43+
44+
func reset() {
45+
#if DEBUG
46+
for (key, value) in data {
47+
assert(value.isEmpty, "Stale entries for runtime data '\(key)'")
48+
}
49+
#endif
50+
data.removeAll(keepingCapacity: false)
51+
}
52+
}
53+
2254
/// The fuzzer instance for which this builder is active.
2355
public let fuzzer: Fuzzer
2456

@@ -147,6 +179,9 @@ public class ProgramBuilder {
147179
/// The remaining CodeGenerators to call as part of a building / CodeGen step, these will "clean up" the state and fix the contexts.
148180
public var scheduled: Stack<GeneratorStub> = Stack()
149181

182+
// Runtime data that can be shared between different stubs within a CodeGenerator.
183+
var runtimeData = GeneratorRuntimeData()
184+
150185
/// Stack of active switch blocks.
151186
private var activeSwitchBlocks = Stack<SwitchBlock>()
152187

@@ -214,6 +249,7 @@ public class ProgramBuilder {
214249
activeObjectLiterals.removeAll()
215250
activeClassDefinitions.removeAll()
216251
buildLog?.reset()
252+
runtimeData.reset()
217253
}
218254

219255
/// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program.
@@ -483,6 +519,36 @@ public class ProgramBuilder {
483519
return probability(0.5) ? randomBuiltinMethodName() : randomCustomMethodName()
484520
}
485521

522+
private static func generateConstrained<T: Equatable>(
523+
_ generator: () -> T,
524+
notIn: any Collection<T>,
525+
fallback: () -> T) -> T {
526+
var result: T
527+
var attempts = 0
528+
repeat {
529+
if attempts >= 10 {
530+
return fallback()
531+
}
532+
result = generator()
533+
attempts += 1
534+
} while notIn.contains(result)
535+
return result
536+
}
537+
538+
// Generate a string not already contained in `notIn` using the provided `generator`. If it fails
539+
// repeatedly, return a random string instead.
540+
public func generateString(_ generator: () -> String, notIn: any Collection<String>) -> String {
541+
Self.generateConstrained(generator, notIn: notIn,
542+
fallback: {String.random(ofLength: Int.random(in: 1...5))})
543+
}
544+
545+
// Find a random variable to use as a string that isn't contained in `notIn`. If it fails
546+
// repeatedly, create a random string literal instead.
547+
public func findOrGenerateStringLikeVariable(notIn: any Collection<Variable>) -> Variable {
548+
return Self.generateConstrained(randomJsVariable, notIn: notIn,
549+
fallback: {loadString(String.random(ofLength: Int.random(in: 1...5)))})
550+
}
551+
486552
// Settings and constants controlling the behavior of randomParameters() below.
487553
// This determines how many variables of a given type need to be visible before
488554
// that type is considered a candidate for a parameter type. For example, if this
@@ -500,15 +566,15 @@ public class ProgramBuilder {
500566
//
501567
// This will attempt to find a parameter types for which at least a few variables of a compatible types are
502568
// currently available to (potentially) later be used as arguments for calling the generated subroutine.
503-
public func randomParameters(n wantedNumberOfParameters: Int? = nil) -> SubroutineDescriptor {
569+
public func randomParameters(n wantedNumberOfParameters: Int? = nil, withRestParameterProbability restProbability: Double = 0.2) -> SubroutineDescriptor {
504570
assert(probabilityOfUsingAnythingAsParameterTypeIfAvoidable >= 0 && probabilityOfUsingAnythingAsParameterTypeIfAvoidable <= 1)
505571

506572
// If the caller didn't specify how many parameters to generated, find an appropriate
507573
// number of parameters based on how many variables are currently visible (and can
508574
// therefore later be used as arguments for calling the new function).
509575
let n: Int
510576
if let requestedN = wantedNumberOfParameters {
511-
assert(requestedN > 0)
577+
assert(requestedN >= 0)
512578
n = requestedN
513579
} else {
514580
switch numberOfVisibleVariables {
@@ -537,15 +603,27 @@ public class ProgramBuilder {
537603
}
538604

539605
var params = ParameterList()
540-
for _ in 0..<n {
606+
607+
let generateRestParameter = n > 0 && probability(restProbability)
608+
let numPlainParams = generateRestParameter ? n - 1 : n
609+
610+
func randomParamType() -> ILType {
541611
if probability(probabilityOfUsingAnythingAsParameterTypeIfAvoidable) {
542-
params.append(.jsAnything)
612+
return .jsAnything
543613
} else {
544-
params.append(.plain(chooseUniform(from: candidates)))
614+
return chooseUniform(from: candidates)
545615
}
546616
}
547617

548-
// TODO: also generate rest parameters and maybe even optional ones sometimes?
618+
for _ in 0..<numPlainParams {
619+
params.append(.plain(randomParamType()))
620+
}
621+
622+
if generateRestParameter {
623+
params.append(.rest(randomParamType()))
624+
}
625+
626+
// TODO: also generate optional parameters sometimes?
549627

550628
return .parameters(params)
551629
}
@@ -686,6 +764,13 @@ public class ProgramBuilder {
686764
// Note that builtin constructors are handled above in the maybeGenerateConstructorAsPath call.
687765
return self.randomVariable(forUseAs: .function())
688766
}),
767+
(.unboundFunction(), {
768+
// TODO: We have the same issue as above for functions.
769+
// First try to find an existing unbound function. if not present, try to find any
770+
// function. Using any function as an unbound function is fine, it just misses the
771+
// information about the receiver type (which for many functions doesn't matter).
772+
return self.randomVariable(ofType: .unboundFunction()) ?? self.randomVariable(forUseAs: .function())
773+
}),
689774
(.undefined, { return self.loadUndefined() }),
690775
(.constructor(), {
691776
// TODO: We have the same issue as above for functions.
@@ -2038,7 +2123,7 @@ public class ProgramBuilder {
20382123

20392124
var numberOfGeneratedInstructions = 0
20402125

2041-
// calculate all input requirements of this CodeGenerator.
2126+
// Calculate all input requirements of this CodeGenerator.
20422127
let inputTypes = Set(generator.parts.reduce([]) { res, gen in
20432128
return res + gen.inputs.types
20442129
})
@@ -3823,17 +3908,20 @@ public class ProgramBuilder {
38233908
}
38243909

38253910
public func wasmTableInit(elementSegment: Variable, table: Variable, tableOffset: Variable, elementSegmentOffset: Variable, nrOfElementsToUpdate: Variable) {
3826-
// TODO: b/427115604 - assert that table.elemType IS_SUBTYPE_OF elementSegment.elemType (depending on refactor outcome).
3911+
let elementSegmentType = ILType.wasmFuncRef
3912+
let tableElemType = b.type(of: table).wasmTableType!.elementType
3913+
assert(elementSegmentType.Is(tableElemType))
3914+
38273915
let addrType = b.type(of: table).wasmTableType!.isTable64 ? ILType.wasmi64 : ILType.wasmi32
38283916
b.emit(WasmTableInit(), withInputs: [elementSegment, table, tableOffset, elementSegmentOffset, nrOfElementsToUpdate],
3829-
types: [.wasmElementSegment(), .object(ofGroup: "WasmTable"), addrType, addrType, addrType])
3917+
types: [.wasmElementSegment(), .object(ofGroup: "WasmTable"), addrType, .wasmi32, .wasmi32])
38303918
}
38313919

38323920
public func wasmTableCopy(dstTable: Variable, srcTable: Variable, dstOffset: Variable, srcOffset: Variable, count: Variable) {
3833-
// TODO: b/427115604 - assert that srcTable.elemType IS_SUBTYPE_OF dstTable.elemType (depending on refactor outcome).
38343921
let dstTableType = b.type(of: dstTable).wasmTableType!
38353922
let srcTableType = b.type(of: srcTable).wasmTableType!
38363923
assert(dstTableType.isTable64 == srcTableType.isTable64)
3924+
assert(srcTableType.elementType.Is(dstTableType.elementType))
38373925

38383926
let addrType = dstTableType.isTable64 ? ILType.wasmi64 : ILType.wasmi32
38393927
b.emit(WasmTableCopy(), withInputs: [dstTable, srcTable, dstOffset, srcOffset, count],
@@ -4354,21 +4442,27 @@ public class ProgramBuilder {
43544442

43554443
@discardableResult
43564444
public func addTable(elementType: ILType, minSize: Int, maxSize: Int? = nil, definedEntries: [WasmTableType.IndexInTableAndWasmSignature] = [], definedEntryValues: [Variable] = [], isTable64: Bool) -> Variable {
4357-
let inputTypes = if elementType == .wasmFuncRef {
4358-
Array(repeating: .wasmFunctionDef() | .function(), count: definedEntries.count)
4359-
} else {
4360-
[ILType]()
4361-
}
4445+
let inputTypes = Array(repeating: getEntryTypeForTable(elementType: elementType), count: definedEntries.count)
43624446
return b.emit(WasmDefineTable(elementType: elementType, limits: Limits(min: minSize, max: maxSize), definedEntries: definedEntries, isTable64: isTable64),
43634447
withInputs: definedEntryValues, types: inputTypes).output
43644448
}
43654449

43664450
@discardableResult
4367-
public func addElementSegment(elementsType: ILType, elements: [Variable]) -> Variable {
4368-
let inputTypes = Array(repeating: elementsType, count: elements.count)
4451+
public func addElementSegment(elements: [Variable]) -> Variable {
4452+
let inputTypes = Array(repeating: getEntryTypeForTable(elementType: ILType.wasmFuncRef), count: elements.count)
43694453
return b.emit(WasmDefineElementSegment(size: UInt32(elements.count)), withInputs: elements, types: inputTypes).output
43704454
}
43714455

4456+
public func getEntryTypeForTable(elementType: ILType) -> ILType {
4457+
switch elementType {
4458+
case .wasmFuncRef:
4459+
return .wasmFunctionDef() | .function()
4460+
default:
4461+
return .object()
4462+
}
4463+
}
4464+
4465+
43724466
// This result can be ignored right now, as we can only define one memory per module
43734467
// Also this should be tracked like a global / table.
43744468
@discardableResult

Sources/Fuzzilli/CodeGen/CodeGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,4 @@ extension CodeGenerator: CustomStringConvertible {
381381
let names = self.parts.map { $0.name }
382382
return names.joined(separator: ",")
383383
}
384-
}
384+
}

Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ public let codeGeneratorWeights = [
192192
"PrototypeOverwriteGenerator": 10,
193193
"CallbackPropertyGenerator": 10,
194194
"MethodCallWithDifferentThisGenerator": 5,
195-
"WeirdClassGenerator": 10,
196195
"ProxyGenerator": 10,
197196
"LengthChangeGenerator": 5,
198197
"ElementKindChangeGenerator": 5,

0 commit comments

Comments
 (0)