Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0f079c6
[tools] Add minimal presubmit script
Liedtke Oct 7, 2025
88d0e33
Add support for table.init and table.copy instructions.
pkk33 Oct 9, 2025
3c15c5c
Revert "Fix order in Opcodes.swift to be in line with program.proto"
Liedtke Oct 10, 2025
bd36b88
Let git in presubmit run in the base directory
mi-ac Oct 10, 2025
6b6240b
[v8] Conditionally enable new flag --wasm-assert-types
Liedtke Oct 9, 2025
5593534
Make presubmit tolerant to staged files and fix directories
mi-ac Oct 10, 2025
bac523f
[v8] Add `--proto-assign-seq-opt` to the fuzzed v8 arguments.
rherouart-collab Oct 16, 2025
8ed7091
Bump V8 timeouts to fix bots
mi-ac Oct 20, 2025
91fb4ab
Pass environment variables to the engine in startup tests (#535)
CSharperMantle Oct 20, 2025
37a7022
Support ILType.unboundFunction in ProgramBuilder.generateTypeInternal
Liedtke Oct 10, 2025
4359c6e
Remove maintainer list from README.md
Liedtke Oct 20, 2025
96a8dc1
Print a debug message after a fatal error
mi-ac Oct 17, 2025
3b4ef3a
[v8] Disable ParitionAlloc
Liedtke Oct 20, 2025
165adb7
Enable the FuzzIL compiler to directly output a JS file
mi-ac Oct 20, 2025
12f601c
Make disposable-variable generators more expressive
mi-ac Oct 20, 2025
6395ef6
[clean-up] Remove redundant context assertions
mi-ac Oct 21, 2025
c2f3b9c
[owners] Increase list of owners
Liedtke Oct 21, 2025
b5a499f
[v8] Add scavenger chaos mode to Fuzzilli
omerktz Oct 21, 2025
8726ea4
[github] Use pre-installed swift version
Liedtke Oct 21, 2025
c1a33c1
Reland "Simplify constrained string generation in code generators"
Liedtke Oct 21, 2025
b1881f2
Revert "Reland "Simplify constrained string generation in code genera…
mi-ac Oct 22, 2025
72eaa81
Reland "Reland "Simplify constrained string generation in code genera…
Liedtke Oct 21, 2025
dbbf7bb
Bump timeout again due to occasional slow flags
mi-ac Oct 22, 2025
36f3477
Fix logic error in comment. Move wasmRefI31 close to wasmI31Ref
pkk33 Oct 22, 2025
edf3297
Fix small error in testWasmAbstractHeapTypeSubsumptionRules.
pkk33 Oct 22, 2025
d06f623
[logging] Sort unordered set entries / map keys before printing
Liedtke Oct 23, 2025
5178400
[v8] Add basic fuzzing for shared JS primitives
Liedtke Oct 22, 2025
f091af3
Enable a dynamic timeout calculation at start-up
mi-ac Oct 27, 2025
771d4c8
Revert "Add --handle-weak-ref-weakly-in-minor-gc to Fuzzilli"
Oct 30, 2025
3e040df
[v8-sandbox] Don't alert on the JS corruption API failing
Liedtke Oct 29, 2025
dcbbafd
Add simple mechanism to share data between stubs in a code generator
Liedtke Oct 27, 2025
59f6bd2
Improve class-definition generation
mi-ac Oct 28, 2025
794c0f0
Add new fuzzing flags for V8
Nov 4, 2025
8a8bf86
[environment] Don't print warning for enum object groups
Liedtke Nov 4, 2025
aab1873
[v8] Move all flags into common profile
Liedtke Nov 5, 2025
5966bb0
[v8] Allow argument randomization for V8SandboxProfile
Liedtke Nov 5, 2025
731e011
[v8] Remove Wasm RAB integration
Liedtke Nov 5, 2025
6c37c6e
[v8] Fix Sandbox fuzzer
Liedtke Nov 6, 2025
1aef794
Add support for rest parameters in functions
Nov 5, 2025
fab4777
[v8] Conditionally enable more maglev flags
Liedtke Nov 7, 2025
80604fc
[v8] Conditionally enable more Turboshaft and Wasm flags
Liedtke Nov 7, 2025
1d1e48b
[v8] Add some more code generators to the sandbox profile
Liedtke Nov 7, 2025
b1e70d4
Add minimal presubmit script
Liedtke Oct 7, 2025
a57629e
Add support for table.init and table.copy instructions.
pkk33 Oct 9, 2025
cfb654b
Revert "Fix order in Opcodes.swift to be in line with program.proto"
Liedtke Oct 10, 2025
7d22ac5
Let git in presubmit run in the base directory
mi-ac Oct 10, 2025
dac359f
Conditionally enable new flag --wasm-assert-types
Liedtke Oct 9, 2025
ce2392d
Make presubmit tolerant to staged files and fix directories
mi-ac Oct 10, 2025
5fb80f9
Add `--proto-assign-seq-opt` to the fuzzed v8 arguments.
rherouart-collab Oct 16, 2025
dfe0643
Bump V8 timeouts to fix bots
mi-ac Oct 20, 2025
8570494
Pass environment variables to the engine in startup tests (#535)
CSharperMantle Oct 20, 2025
6f6d028
Support ILType.unboundFunction in ProgramBuilder.generateTypeInternal
Liedtke Oct 10, 2025
750aeb5
Remove maintainer list from README.md
Liedtke Oct 20, 2025
8f7161d
Print a debug message after a fatal error
mi-ac Oct 17, 2025
29b5968
Disable ParitionAlloc
Liedtke Oct 20, 2025
d3aafd2
Enable the FuzzIL compiler to directly output a JS file
mi-ac Oct 20, 2025
85ec023
Make disposable-variable generators more expressive
mi-ac Oct 20, 2025
2b2e6f9
Remove redundant context assertions
mi-ac Oct 21, 2025
16bd60c
Increase list of owners
Liedtke Oct 21, 2025
f178ca7
Add scavenger chaos mode to Fuzzilli
omerktz Oct 21, 2025
8c1414c
Merge remote-tracking branch 'upstream/main'
chase1k Nov 13, 2025
5fd1e5a
Swift build fixes
chase1k Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 25-nightly

- name: Swift Version
run: swift --version
# Is it failing to run tests b/c we're overriding
# what swift binary to use?
- name: Setup Swift for Ubuntu
Expand All @@ -43,6 +44,13 @@ jobs:
- uses: actions/checkout@v2
- name: Build
run: swift build -c ${{ matrix.kind }} -v
- name: Install protobuf
# Install protoc so the presubmit can also validate the generated *.pb.swift files.
if: ${{ matrix.os == 'ubuntu-latest' && matrix.kind == 'debug' }}
run: sudo apt install -y protobuf-compiler
- name: Run presubmit checks
if: ${{ matrix.os == 'ubuntu-latest' && matrix.kind == 'debug' }}
run: python3 Tools/presubmit.py
- name: Run tests with Node.js
run: swift test -c ${{ matrix.kind }} -v
- name: Install jsvu
Expand Down
4 changes: 4 additions & 0 deletions OWNERS
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
cffsmith@google.com
machenbach@google.com
mdanylo@google.com
mliedtke@google.com
pawkra@google.com
saelo@google.com
tacet@google.com

per-file WHITESPACE=*
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ let package = Package(
.library(name: "Fuzzilli",targets: ["Fuzzilli"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.32.0"),
.package(
url: "https://github.com/apple/swift-collections.git",
.upToNextMinor(from: "1.2.0")
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

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

Fuzzilli is developed and maintained by:
- Samuel Groß, <saelo@google.com>
- Carl Smith, <cffsmith@google.com>

## Usage

The basic steps to use this fuzzer are:
Expand Down
33 changes: 22 additions & 11 deletions Sources/FuzzILTool/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func loadProgramOrExit(from path: String) -> Program {

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

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

print(fuzzILLifter.lift(program))
print()
print(jsLifter.lift(program))
if let js_path = args["--outputPathJS"] {
let content = jsLifter.lift(program)
do {
try content.write(to: URL(fileURLWithPath: js_path), atomically: false, encoding: String.Encoding.utf8)
} catch {
print("Failed to write file \(js_path): \(error)")
exit(-1)
}
} else {
print(fuzzILLifter.lift(program))
print()
print(jsLifter.lift(program))

do {
let outputPath = URL(fileURLWithPath: path).deletingPathExtension().appendingPathExtension("fzil")
try program.asProtobuf().serializedData().write(to: outputPath)
print("FuzzIL program written to \(outputPath.relativePath)")
} catch {
print("Failed to store output program to disk: \(error)")
exit(-1)
do {
let outputPath = URL(fileURLWithPath: path).deletingPathExtension().appendingPathExtension("fzil")
try program.asProtobuf().serializedData().write(to: outputPath)
print("FuzzIL program written to \(outputPath.relativePath)")
} catch {
print("Failed to store output program to disk: \(error)")
exit(-1)
}
}
}

Expand Down
128 changes: 111 additions & 17 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,38 @@ import Foundation
/// This provides methods for constructing and appending random
/// instances of the different kinds of operations in a program.
public class ProgramBuilder {

/// Runtime data used by code generators to share data between different stubs inside the same
/// code generator. It is strictly required that all pushed values are also popped again in the
/// same code generator.
public class GeneratorRuntimeData {
private var data = [String : Stack<Variable>]()

public func push(_ key: String, _ value: Variable) {
data[key, default: .init()].push(value)
}

public func pop(_ key: String) -> Variable {
assert(data[key] != nil)
return data[key]!.pop()
}

// Fetch the most recent value for this key and push it back.
public func popAndPush(_ key: String) -> Variable {
assert(data[key] != nil)
return data[key]!.top
}

func reset() {
#if DEBUG
for (key, value) in data {
assert(value.isEmpty, "Stale entries for runtime data '\(key)'")
}
#endif
data.removeAll(keepingCapacity: false)
}
}

/// The fuzzer instance for which this builder is active.
public let fuzzer: Fuzzer

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

// Runtime data that can be shared between different stubs within a CodeGenerator.
var runtimeData = GeneratorRuntimeData()

/// Stack of active switch blocks.
private var activeSwitchBlocks = Stack<SwitchBlock>()

Expand Down Expand Up @@ -214,6 +249,7 @@ public class ProgramBuilder {
activeObjectLiterals.removeAll()
activeClassDefinitions.removeAll()
buildLog?.reset()
runtimeData.reset()
}

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

private static func generateConstrained<T: Equatable>(
_ generator: () -> T,
notIn: any Collection<T>,
fallback: () -> T) -> T {
var result: T
var attempts = 0
repeat {
if attempts >= 10 {
return fallback()
}
result = generator()
attempts += 1
} while notIn.contains(result)
return result
}

// Generate a string not already contained in `notIn` using the provided `generator`. If it fails
// repeatedly, return a random string instead.
public func generateString(_ generator: () -> String, notIn: any Collection<String>) -> String {
Self.generateConstrained(generator, notIn: notIn,
fallback: {String.random(ofLength: Int.random(in: 1...5))})
}

// Find a random variable to use as a string that isn't contained in `notIn`. If it fails
// repeatedly, create a random string literal instead.
public func findOrGenerateStringLikeVariable(notIn: any Collection<Variable>) -> Variable {
return Self.generateConstrained(randomJsVariable, notIn: notIn,
fallback: {loadString(String.random(ofLength: Int.random(in: 1...5)))})
}

// Settings and constants controlling the behavior of randomParameters() below.
// This determines how many variables of a given type need to be visible before
// that type is considered a candidate for a parameter type. For example, if this
Expand All @@ -500,15 +566,15 @@ public class ProgramBuilder {
//
// This will attempt to find a parameter types for which at least a few variables of a compatible types are
// currently available to (potentially) later be used as arguments for calling the generated subroutine.
public func randomParameters(n wantedNumberOfParameters: Int? = nil) -> SubroutineDescriptor {
public func randomParameters(n wantedNumberOfParameters: Int? = nil, withRestParameterProbability restProbability: Double = 0.2) -> SubroutineDescriptor {
assert(probabilityOfUsingAnythingAsParameterTypeIfAvoidable >= 0 && probabilityOfUsingAnythingAsParameterTypeIfAvoidable <= 1)

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

var params = ParameterList()
for _ in 0..<n {

let generateRestParameter = n > 0 && probability(restProbability)
let numPlainParams = generateRestParameter ? n - 1 : n

func randomParamType() -> ILType {
if probability(probabilityOfUsingAnythingAsParameterTypeIfAvoidable) {
params.append(.jsAnything)
return .jsAnything
} else {
params.append(.plain(chooseUniform(from: candidates)))
return chooseUniform(from: candidates)
}
}

// TODO: also generate rest parameters and maybe even optional ones sometimes?
for _ in 0..<numPlainParams {
params.append(.plain(randomParamType()))
}

if generateRestParameter {
params.append(.rest(randomParamType()))
}

// TODO: also generate optional parameters sometimes?

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

var numberOfGeneratedInstructions = 0

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

public func wasmTableInit(elementSegment: Variable, table: Variable, tableOffset: Variable, elementSegmentOffset: Variable, nrOfElementsToUpdate: Variable) {
// TODO: b/427115604 - assert that table.elemType IS_SUBTYPE_OF elementSegment.elemType (depending on refactor outcome).
let elementSegmentType = ILType.wasmFuncRef
let tableElemType = b.type(of: table).wasmTableType!.elementType
assert(elementSegmentType.Is(tableElemType))

let addrType = b.type(of: table).wasmTableType!.isTable64 ? ILType.wasmi64 : ILType.wasmi32
b.emit(WasmTableInit(), withInputs: [elementSegment, table, tableOffset, elementSegmentOffset, nrOfElementsToUpdate],
types: [.wasmElementSegment(), .object(ofGroup: "WasmTable"), addrType, addrType, addrType])
types: [.wasmElementSegment(), .object(ofGroup: "WasmTable"), addrType, .wasmi32, .wasmi32])
}

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

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

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

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

public func getEntryTypeForTable(elementType: ILType) -> ILType {
switch elementType {
case .wasmFuncRef:
return .wasmFunctionDef() | .function()
default:
return .object()
}
}


// This result can be ignored right now, as we can only define one memory per module
// Also this should be tracked like a global / table.
@discardableResult
Expand Down
2 changes: 1 addition & 1 deletion Sources/Fuzzilli/CodeGen/CodeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,4 @@ extension CodeGenerator: CustomStringConvertible {
let names = self.parts.map { $0.name }
return names.joined(separator: ",")
}
}
}
1 change: 0 additions & 1 deletion Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ public let codeGeneratorWeights = [
"PrototypeOverwriteGenerator": 10,
"CallbackPropertyGenerator": 10,
"MethodCallWithDifferentThisGenerator": 5,
"WeirdClassGenerator": 10,
"ProxyGenerator": 10,
"LengthChangeGenerator": 5,
"ElementKindChangeGenerator": 5,
Expand Down
Loading