Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 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
926cc0b
Fix?
Aeshus Oct 9, 2025
7aa811f
Fix
Aeshus 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
3dab5f6
Merge remote-tracking branch 'upstream/main'
Aeshus Oct 23, 2025
c0efde6
Merge remote-tracking branch 'vrig/main'
Aeshus Oct 23, 2025
b16ca24
Hope
Aeshus Oct 28, 2025
4bc33c6
Why
Aeshus Oct 28, 2025
3aac185
Test
Aeshus Oct 29, 2025
6efa527
PLEASE
Aeshus Oct 29, 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
58 changes: 46 additions & 12 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,78 @@ jobs:
build_test:
timeout-minutes: 30
strategy:
# If macos-latest fails, we still don't want to cancel ubuntu-latest or the other way around.
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
kind: [debug]
include:
# On linux also build and test release.
- os: ubuntu-latest
kind: release

runs-on: ${{ matrix.os }}

env:
SWIFT_VERSION: 6.1
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/fuzzilli

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: fuzzilli
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=5s
--health-timeout=5s
--health-retries=10

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 25-nightly
node-version: 20

# Is it failing to run tests b/c we're overriding
# what swift binary to use?
- name: Setup Swift for Ubuntu
- name: Setup Swift (Linux only)
if: runner.os == 'Linux'
run: |
sudo apt-get update -q
sudo apt-get install -y libicu-dev libxml2 libsqlite3-dev libblocksruntime-dev tzdata
wget -q https://download.swift.org/swift-${SWIFT_VERSION}-release/ubuntu2204/swift-${SWIFT_VERSION}-RELEASE/swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04.tar.gz
tar xzf swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04.tar.gz
mv swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04 /opt/swift
rm swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04.tar.gz
export PATH="/opt/swift/usr/bin:${PATH}"
- uses: actions/checkout@v2
sudo mv swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04 /opt/swift
echo "/opt/swift/usr/bin" | sudo tee -a /etc/environment
echo "/opt/swift/usr/bin" >> $GITHUB_PATH

- name: Wait for PostgreSQL
run: |
echo "Waiting for PostgreSQL to become ready..."
for i in {1..20}; do
pg_isready -h localhost -p 5432 -U postgres && break
sleep 2
done
psql $DATABASE_URL -c 'SELECT version();'

- name: Build
run: swift build -c ${{ matrix.kind }} -v
- name: Run tests with Node.js

- name: Run tests
env:
DATABASE_URL: ${{ env.DATABASE_URL }}
run: swift test -c ${{ matrix.kind }} -v

- name: Install jsvu
run: npm install jsvu -g

- name: Install d8
run: jsvu --os=default --engines=v8

- name: Run tests with d8
run: FUZZILLI_TEST_SHELL=~/.jsvu/engines/v8/v8 swift test -c ${{ matrix.kind }} -v
env:
FUZZILLI_TEST_SHELL: ~/.jsvu/engines/v8/v8
DATABASE_URL: ${{ env.DATABASE_URL }}
run: swift test -c ${{ matrix.kind }} -v
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=*
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
66 changes: 56 additions & 10 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,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 Down Expand Up @@ -686,6 +716,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 @@ -3823,17 +3860,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 +4394,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
Loading
Loading