diff --git a/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift b/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift new file mode 100644 index 000000000..00243b5d8 --- /dev/null +++ b/Sources/FuzzilliCli/Profiles/V8CommonProfile.swift @@ -0,0 +1,570 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Fuzzilli + +public extension ILType { + static let jsD8 = ILType.object(ofGroup: "D8", withProperties: ["test"], withMethods: []) + + static let jsD8Test = ILType.object(ofGroup: "D8Test", withProperties: ["FastCAPI"], withMethods: []) + + static let jsD8FastCAPI = ILType.object(ofGroup: "D8FastCAPI", withProperties: [], withMethods: ["throw_no_fallback", "add_32bit_int"]) + + static let jsD8FastCAPIConstructor = ILType.constructor([] => .jsD8FastCAPI) + + static let gcTypeEnum = ILType.enumeration(ofName: "gcType", withValues: ["minor", "major"]) + static let gcExecutionEnum = ILType.enumeration(ofName: "gcExecution", withValues: ["async", "sync"]) +} + +public let gcOptions = ObjectGroup( + name: "GCOptions", + instanceType: .object(ofGroup: "GCOptions", withProperties: ["type", "execution"], withMethods: []), + properties: ["type": .gcTypeEnum, + "execution": .gcExecutionEnum], + methods: [:]) + +public let fastCallables : [(group: ILType, method: String)] = [ + (group: .jsD8FastCAPI, method: "throw_no_fallback"), + (group: .jsD8FastCAPI, method: "add_32bit_int"), +] + +// Insert random GC calls throughout our code. +public let V8GcGenerator = CodeGenerator("GcGenerator") { b in + let gc = b.createNamedVariable(forBuiltin: "gc") + + // `gc()` takes a `type` parameter. If the value is 'async', gc() returns a + // Promise. We currently do not really handle this other than typing the + // return of gc to .undefined | .jsPromise. One could either chain a .then + // or create two wrapper functions that are differently typed such that + // fuzzilli always knows what the type of the return value is. + b.callFunction(gc, withArgs: b.findOrGenerateArguments(forSignature: b.fuzzer.environment.type(ofBuiltin: "gc").signature!)) +} + +// Insert random GC calls throughout our code. +public let V8GcSandboxGenerator = CodeGenerator("GcGenerator") { b in + let gc = b.createNamedVariable(forBuiltin: "gc") + + // Do minor GCs more frequently. + let type = b.loadString(probability(0.25) ? "major" : "minor") + // If the execution type is 'async', gc() returns a Promise, we currently + // do not really handle other than typing the return of gc to .undefined | + // .jsPromise. One could either chain a .then or create two wrapper + // functions that are differently typed such that fuzzilli always knows + // what the type of the return value is. + let execution = b.loadString(probability(0.5) ? "sync" : "async") + b.callFunction(gc, withArgs: [b.createObject(with: ["type": type, "execution": execution])]) +} + +public let ForceJITCompilationThroughLoopGenerator = CodeGenerator("ForceJITCompilationThroughLoopGenerator", inputs: .required(.function())) { b, f in + assert(b.type(of: f).Is(.function())) + let arguments = b.randomArguments(forCalling: f) + + b.buildRepeatLoop(n: 100) { _ in + b.callFunction(f, withArgs: arguments) + } +} + +public let ForceTurboFanCompilationGenerator = CodeGenerator("ForceTurboFanCompilationGenerator", inputs: .required(.function())) { b, f in + assert(b.type(of: f).Is(.function())) + let arguments = b.randomArguments(forCalling: f) + + b.callFunction(f, withArgs: arguments) + + b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); + + b.callFunction(f, withArgs: arguments) + b.callFunction(f, withArgs: arguments) + + b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); + + b.callFunction(f, withArgs: arguments) +} + +public let ForceMaglevCompilationGenerator = CodeGenerator("ForceMaglevCompilationGenerator", inputs: .required(.function())) { b, f in + assert(b.type(of: f).Is(.function())) + let arguments = b.randomArguments(forCalling: f) + + b.callFunction(f, withArgs: arguments) + + b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); + + b.callFunction(f, withArgs: arguments) + b.callFunction(f, withArgs: arguments) + + b.eval("%OptimizeMaglevOnNextCall(%@)", with: [f]); + + b.callFunction(f, withArgs: arguments) +} + +public let TurbofanVerifyTypeGenerator = CodeGenerator("TurbofanVerifyTypeGenerator", inputs: .one) { b, v in + b.eval("%VerifyType(%@)", with: [v]) +} + +public let WorkerGenerator = RecursiveCodeGenerator("WorkerGenerator") { b in + let workerSignature = Signature(withParameterCount: Int.random(in: 0...3)) + + // TODO(cffsmith): currently Fuzzilli does not know that this code is sent + // to another worker as a string. This has the consequence that we might + // use variables inside the worker that are defined in a different scope + // and as such they are not accessible / undefined. To fix this we should + // define an Operation attribute that tells Fuzzilli to ignore variables + // defined in outer scopes. + let workerFunction = b.buildPlainFunction(with: .parameters(workerSignature.parameters)) { args in + let this = b.loadThis() + + // Generate a random onmessage handler for incoming messages. + let onmessageFunction = b.buildPlainFunction(with: .parameters(n: 1)) { args in + b.buildRecursive(block: 1, of: 2) + } + b.setProperty("onmessage", of: this, to: onmessageFunction) + + b.buildRecursive(block: 2, of: 2) + } + let workerConstructor = b.createNamedVariable(forBuiltin: "Worker") + + let functionString = b.loadString("function") + let argumentsArray = b.createArray(with: b.randomArguments(forCalling: workerFunction)) + + let configObject = b.createObject(with: ["type": functionString, "arguments": argumentsArray]) + + let worker = b.construct(workerConstructor, withArgs: [workerFunction, configObject]) + // Fuzzilli can now use the worker. +} + +public let WasmStructGenerator = CodeGenerator("WasmStructGenerator") { b in + b.eval("%WasmStruct()", hasOutput: true); +} + +public let WasmArrayGenerator = CodeGenerator("WasmArrayGenerator") { b in + b.eval("%WasmArray()", hasOutput: true); +} + +public let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in + // This template is meant to stress the v8 Map transition mechanisms. + // Basically, it generates a bunch of CreateObject, GetProperty, SetProperty, FunctionDefinition, + // and CallFunction operations operating on a small set of objects and property names. + + let propertyNames = b.fuzzer.environment.customProperties + assert(Set(propertyNames).isDisjoint(with: b.fuzzer.environment.customMethods)) + + // Use this as base object type. For one, this ensures that the initial map is stable. + // Moreover, this guarantees that when querying for this type, we will receive one of + // the objects we created and not e.g. a function (which is also an object). + assert(propertyNames.contains("a")) + let objType = ILType.object(withProperties: ["a"]) + + // Helper function to pick random properties and values. + func randomProperties(in b: ProgramBuilder) -> ([String], [Variable]) { + if !b.hasVisibleVariables { + // Use integer values if there are no visible variables, which should be a decent fallback. + b.loadInt(b.randomInt()) + } + + var properties = ["a"] + var values = [b.randomJsVariable()] + for _ in 0..<3 { + let property = chooseUniform(from: propertyNames) + guard !properties.contains(property) else { continue } + properties.append(property) + values.append(b.randomJsVariable()) + } + assert(Set(properties).count == values.count) + return (properties, values) + } + + // Temporarily overwrite the active code generators with the following generators... + let primitiveValueGenerator = ValueGenerator("PrimitiveValue") { b, n in + for _ in 0..([ + (primitiveValueGenerator, 2), + (createObjectGenerator, 1), + (objectMakerGenerator, 1), + (objectConstructorGenerator, 1), + (objectClassGenerator, 1), + + (propertyStoreGenerator, 10), + (propertyLoadGenerator, 10), + (propertyConfigureGenerator, 5), + (functionDefinitionGenerator, 2), + (functionCallGenerator, 3), + (constructorCallGenerator, 2), + (functionJitCallGenerator, 2) + ])) + + // ... run some of the ValueGenerators to create some initial objects ... + b.buildPrefix() + // ... and generate a bunch of code. + b.build(n: 100, by: .generating) + + // Now, restore the previous code generators and generate some more code. + b.fuzzer.setCodeGenerators(prevCodeGenerators) + b.build(n: 10) + + // Finally, run HeapObjectVerify on all our generated objects (that are still in scope). + for obj in b.visibleVariables where b.type(of: obj).Is(objType) { + b.eval("%HeapObjectVerify(%@)", with: [obj]) + } +} + +public let ValueSerializerFuzzer = ProgramTemplate("ValueSerializerFuzzer") { b in + b.buildPrefix() + + // Create some random values that can be serialized below. + b.build(n: 50) + + // Load necessary builtins + let d8 = b.createNamedVariable(forBuiltin: "d8") + let serializer = b.getProperty("serializer", of: d8) + let Uint8Array = b.createNamedVariable(forBuiltin: "Uint8Array") + + // Serialize a random object + let content = b.callMethod("serialize", on: serializer, withArgs: [b.randomJsVariable()]) + let u8 = b.construct(Uint8Array, withArgs: [content]) + + // Choose a random byte to change + let index = Int64.random(in: 0..<100) + + // Either flip or replace the byte + let newByte: Variable + if probability(0.5) { + let bit = b.loadInt(1 << Int.random(in: 0..<8)) + let oldByte = b.getElement(index, of: u8) + newByte = b.binary(oldByte, bit, with: .Xor) + } else { + newByte = b.loadInt(Int64.random(in: 0..<256)) + } + b.setElement(index, of: u8, to: newByte) + + // Deserialize the resulting buffer + let _ = b.callMethod("deserialize", on: serializer, withArgs: [content]) + + // Generate some more random code to (hopefully) use the deserialized objects in some interesting way. + b.build(n: 10) +} + +// This template fuzzes the RegExp engine. +// It finds bugs like: crbug.com/1437346 and crbug.com/1439691. +public let V8RegExpFuzzer = ProgramTemplate("RegExpFuzzer") { b in + // Taken from: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:v8/test/fuzzer/regexp-builtins.cc;l=212;drc=a61b95c63b0b75c1cfe872d9c8cdf927c226046e + let twoByteSubjectString = "f\\uD83D\\uDCA9ba\\u2603" + + let replacementCandidates = [ + "'X'", + "'$1$2$3'", + "'$$$&$`$\\'$1'", + "() => 'X'", + "(arg0, arg1, arg2, arg3, arg4) => arg0 + arg1 + arg2 + arg3 + arg4", + "() => 42" + ] + + let lastIndices = [ + "undefined", "-1", "0", + "1", "2", "3", + "4", "5", "6", + "7", "8", "9", + "50", "4294967296", "2147483647", + "2147483648", "NaN", "Not a Number" + ] + + let f = b.buildPlainFunction(with: .parameters(n: 0)) { _ in + let (pattern, flags) = b.randomRegExpPatternAndFlags() + let regExpVar = b.loadRegExp(pattern, flags) + + let lastIndex = chooseUniform(from: lastIndices) + let lastIndexString = b.loadString(lastIndex) + + b.setProperty("lastIndex", of: regExpVar, to: lastIndexString) + + let subjectVar: Variable + + if probability(0.1) { + subjectVar = b.loadString(twoByteSubjectString) + } else { + subjectVar = b.loadString(b.randomString()) + } + + let resultVar = b.loadNull() + + b.buildTryCatchFinally(tryBody: { + let symbol = b.createNamedVariable(forBuiltin: "Symbol") + withEqualProbability({ + let res = b.callMethod("exec", on: regExpVar, withArgs: [subjectVar]) + b.reassign(resultVar, to: res) + }, { + let prop = b.getProperty("match", of: symbol) + let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar]) + b.reassign(resultVar, to: res) + }, { + let prop = b.getProperty("replace", of: symbol) + let replacement = withEqualProbability({ + b.loadString(b.randomString()) + }, { + b.loadString(chooseUniform(from: replacementCandidates)) + }) + let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar, replacement]) + b.reassign(resultVar, to: res) + }, { + let prop = b.getProperty("search", of: symbol) + let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar]) + b.reassign(resultVar, to: res) + }, { + let prop = b.getProperty("split", of: symbol) + let randomSplitLimit = withEqualProbability({ + "undefined" + }, { + "'not a number'" + }, { + String(b.randomInt()) + }) + let limit = b.loadString(randomSplitLimit) + let res = b.callComputedMethod(symbol, on: regExpVar, withArgs: [subjectVar, limit]) + b.reassign(resultVar, to: res) + }, { + let res = b.callMethod("test", on: regExpVar, withArgs: [subjectVar]) + b.reassign(resultVar, to: res) + }) + }, catchBody: { _ in + }) + + b.build(n: 7) + + b.doReturn(resultVar) + } + + b.eval("%SetForceSlowPath(false)"); + // compile the regexp once + b.callFunction(f) + let resFast = b.callFunction(f) + b.eval("%SetForceSlowPath(true)"); + let resSlow = b.callFunction(f) + b.eval("%SetForceSlowPath(false)"); + + b.build(n: 15) +} + +// Emits calls with recursive calls of limited depth. +public let LazyDeoptFuzzer = ProgramTemplate("LazyDeoptFuzzer") { b in + b.buildPrefix() + b.build(n: 30) + + let counter = b.loadInt(0) + let max = b.loadInt(Int64.random(in: 2...5)) + let params = b.randomParameters() + let dummyFct = b.buildPlainFunction(with: params) { args in + b.loadString("Dummy function for emitting recursive call") + } + let realFct = b.buildPlainFunction(with: params) { args in + b.build(n: 10) + + b.buildIf(b.compare(counter, with: max, using: .lessThan)) { + b.reassign(counter, to: b.binary(counter, b.loadInt(1), with: .Add)) + b.callFunction(dummyFct, withArgs: b.randomArguments(forCalling: dummyFct)) + } + // Mark the function for deoptimization. Due to the recursive pattern above, on the outer + // stack frames this should trigger a lazy deoptimization. + b.eval("%DeoptimizeNow();"); + b.build(n: 30) + b.doReturn(b.randomJsVariable()) + } + + // Turn the call into a recursive call. + b.reassign(dummyFct, to: realFct) + let args = b.randomArguments(forCalling: realFct) + b.eval("%PrepareFunctionForOptimization(%@)", with: [realFct]); + b.callFunction(realFct, withArgs: args) + b.eval("%OptimizeFunctionOnNextCall(%@)", with: [realFct]); + // Call the function. + b.callFunction(realFct, withArgs: args) +} + + +public let jsD8 = ObjectGroup(name: "D8", instanceType: .jsD8, properties: ["test" : .jsD8Test], methods: [:]) + +public let jsD8Test = ObjectGroup(name: "D8Test", instanceType: .jsD8Test, properties: ["FastCAPI": .jsD8FastCAPIConstructor], methods: [:]) + +public let jsD8FastCAPI = ObjectGroup(name: "D8FastCAPI", instanceType: .jsD8FastCAPI, properties: [:], + methods:["throw_no_fallback": [] => .integer, + "add_32bit_int": [.integer, .integer] => .integer + ]) + +public let WasmFastCallFuzzer = WasmProgramTemplate("WasmFastCallFuzzer") { b in + b.buildPrefix() + b.build(n: 10) + let target = fastCallables.randomElement()! + let apiObj = b.findOrGenerateType(target.group) + + // Bind the API function so that it can be called from WebAssembly. + let wrapped = b.bindMethod(target.method, on: apiObj) + + let functionSig = chooseUniform(from: b.methodSignatures(of: target.method, on: target.group)) + let wrappedSig = [.plain(b.type(of: apiObj))] + functionSig.parameters => functionSig.outputType + + let m = b.buildWasmModule { m in + let allWasmTypes: WeightedList = WeightedList([(.wasmi32, 1), (.wasmi64, 1), (.wasmf32, 1), (.wasmf64, 1), (.wasmExternRef, 1), (.wasmFuncRef, 1)]) + let wasmSignature = ProgramBuilder.convertJsSignatureToWasmSignature(wrappedSig, availableTypes: allWasmTypes) + m.addWasmFunction(with: wasmSignature) {fbuilder, _, _ in + let args = b.randomWasmArguments(forWasmSignature: wasmSignature) + if let args { + let maybeRet = fbuilder.wasmJsCall(function: wrapped, withArgs: args, withWasmSignature: wasmSignature) + if let ret = maybeRet { + return [ret] + } + } else { + logger.error("Arguments should have been generated") + } + return wasmSignature.outputTypes.map(fbuilder.findOrGenerateWasmVar) + } + } + + let exports = m.loadExports() + + for (methodName, _) in m.getExportedMethods() { + let exportedMethod = b.getProperty(methodName, of: exports) + b.eval("%WasmTierUpFunction(%@)", with: [exportedMethod]) + let args = b.findOrGenerateArguments(forSignature: wrappedSig) + b.callMethod(methodName, on: exports, withArgs: args) + } +} + +public let FastApiCallFuzzer = ProgramTemplate("FastApiCallFuzzer") { b in + b.buildPrefix() + b.build(n: 20) + let parameterCount = probability(0.5) ? 0 : Int.random(in: 1...4) + + let f = b.buildPlainFunction(with: .parameters(n: parameterCount)) { args in + b.build(n: 10) + let target = fastCallables.randomElement()! + let apiObj = b.findOrGenerateType(target.group) + let functionSig = chooseUniform(from: b.methodSignatures(of: target.method, on: target.group)) + let apiCall = b.callMethod(target.method, on: apiObj, withArgs: b.findOrGenerateArguments(forSignature: functionSig), guard: true) + b.doReturn(apiCall) + } + + let args = b.randomJsVariables(n: Int.random(in: 0...5)) + b.callFunction(f, withArgs: args) + + b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); + + b.callFunction(f, withArgs: args) + b.callFunction(f, withArgs: args) + + b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); + + b.callFunction(f, withArgs: args) + + b.build(n: 10) +} diff --git a/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift b/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift index ce65ef813..3162811c1 100644 --- a/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8HoleFuzzingProfile.swift @@ -14,48 +14,6 @@ import Fuzzilli -// TODO: move common parts (e.g. generators) into a V8CommonProfile.swift. -fileprivate let ForceJITCompilationThroughLoopGenerator = CodeGenerator("ForceJITCompilationThroughLoopGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - b.buildRepeatLoop(n: 100) { _ in - b.callFunction(f, withArgs: arguments) - } -} -fileprivate let ForceTurboFanCompilationGenerator = CodeGenerator("ForceTurboFanCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - b.callFunction(f, withArgs: arguments) - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); - b.callFunction(f, withArgs: arguments) -} -fileprivate let ForceMaglevCompilationGenerator = CodeGenerator("ForceMaglevCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - b.callFunction(f, withArgs: arguments) - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - b.eval("%OptimizeMaglevOnNextCall(%@)", with: [f]); - b.callFunction(f, withArgs: arguments) -} -// Insert random GC calls throughout our code. -fileprivate let GcGenerator = CodeGenerator("GcGenerator") { b in - let gc = b.createNamedVariable(forBuiltin: "gc") - // Do minor GCs more frequently. - let type = b.loadString(probability(0.25) ? "major" : "minor") - // If the execution type is 'async', gc() returns a Promise, we currently - // do not really handle other than typing the return of gc to .undefined | - // .jsPromise. One could either chain a .then or create two wrapper - // functions that are differently typed such that fuzzilli always knows - // what the type of the return value is. - let execution = b.loadString(probability(0.5) ? "sync" : "async") - b.callFunction(gc, withArgs: [b.createObject(with: ["type": type, "execution": execution])]) -} - // This value generator inserts Hole leaks into the program. Use this if you // want to fuzz for Memory Corruption using holes, this should be used in // conjunction with the --hole-fuzzing runtime flag. @@ -106,7 +64,7 @@ let v8HoleFuzzingProfile = Profile( (ForceJITCompilationThroughLoopGenerator, 5), (ForceTurboFanCompilationGenerator, 5), (ForceMaglevCompilationGenerator, 5), - (GcGenerator, 10), + (V8GcGenerator, 10), (HoleLeakGenerator, 25), ], additionalProgramTemplates: WeightedList([ diff --git a/Sources/FuzzilliCli/Profiles/V8Profile.swift b/Sources/FuzzilliCli/Profiles/V8Profile.swift index 68dcda01b..6b6cc5537 100644 --- a/Sources/FuzzilliCli/Profiles/V8Profile.swift +++ b/Sources/FuzzilliCli/Profiles/V8Profile.swift @@ -14,543 +14,6 @@ import Fuzzilli -fileprivate let ForceJITCompilationThroughLoopGenerator = CodeGenerator("ForceJITCompilationThroughLoopGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.buildRepeatLoop(n: 100) { _ in - b.callFunction(f, withArgs: arguments) - } -} - -fileprivate let ForceTurboFanCompilationGenerator = CodeGenerator("ForceTurboFanCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.callFunction(f, withArgs: arguments) - - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - - b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) -} - -fileprivate let ForceMaglevCompilationGenerator = CodeGenerator("ForceMaglevCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.callFunction(f, withArgs: arguments) - - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - - b.eval("%OptimizeMaglevOnNextCall(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) -} - -fileprivate let TurbofanVerifyTypeGenerator = CodeGenerator("TurbofanVerifyTypeGenerator", inputs: .one) { b, v in - b.eval("%VerifyType(%@)", with: [v]) -} - -fileprivate let WorkerGenerator = RecursiveCodeGenerator("WorkerGenerator") { b in - let workerSignature = Signature(withParameterCount: Int.random(in: 0...3)) - - // TODO(cffsmith): currently Fuzzilli does not know that this code is sent - // to another worker as a string. This has the consequence that we might - // use variables inside the worker that are defined in a different scope - // and as such they are not accessible / undefined. To fix this we should - // define an Operation attribute that tells Fuzzilli to ignore variables - // defined in outer scopes. - let workerFunction = b.buildPlainFunction(with: .parameters(workerSignature.parameters)) { args in - let this = b.loadThis() - - // Generate a random onmessage handler for incoming messages. - let onmessageFunction = b.buildPlainFunction(with: .parameters(n: 1)) { args in - b.buildRecursive(block: 1, of: 2) - } - b.setProperty("onmessage", of: this, to: onmessageFunction) - - b.buildRecursive(block: 2, of: 2) - } - let workerConstructor = b.createNamedVariable(forBuiltin: "Worker") - - let functionString = b.loadString("function") - let argumentsArray = b.createArray(with: b.randomArguments(forCalling: workerFunction)) - - let configObject = b.createObject(with: ["type": functionString, "arguments": argumentsArray]) - - let worker = b.construct(workerConstructor, withArgs: [workerFunction, configObject]) - // Fuzzilli can now use the worker. -} - -// Insert random GC calls throughout our code. -fileprivate let GcGenerator = CodeGenerator("GcGenerator") { b in - let gc = b.createNamedVariable(forBuiltin: "gc") - - // `gc()` takes a `type` parameter. If the value is 'async', gc() returns a - // Promise. We currently do not really handle this other than typing the - // return of gc to .undefined | .jsPromise. One could either chain a .then - // or create two wrapper functions that are differently typed such that - // fuzzilli always knows what the type of the return value is. - b.callFunction(gc, withArgs: b.findOrGenerateArguments(forSignature: b.fuzzer.environment.type(ofBuiltin: "gc").signature!)) } - -fileprivate let WasmStructGenerator = CodeGenerator("WasmStructGenerator") { b in - b.eval("%WasmStruct()", hasOutput: true); -} - -fileprivate let WasmArrayGenerator = CodeGenerator("WasmArrayGenerator") { b in - b.eval("%WasmArray()", hasOutput: true); -} - -fileprivate let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in - // This template is meant to stress the v8 Map transition mechanisms. - // Basically, it generates a bunch of CreateObject, GetProperty, SetProperty, FunctionDefinition, - // and CallFunction operations operating on a small set of objects and property names. - - let propertyNames = b.fuzzer.environment.customProperties - assert(Set(propertyNames).isDisjoint(with: b.fuzzer.environment.customMethods)) - - // Use this as base object type. For one, this ensures that the initial map is stable. - // Moreover, this guarantees that when querying for this type, we will receive one of - // the objects we created and not e.g. a function (which is also an object). - assert(propertyNames.contains("a")) - let objType = ILType.object(withProperties: ["a"]) - - // Helper function to pick random properties and values. - func randomProperties(in b: ProgramBuilder) -> ([String], [Variable]) { - if !b.hasVisibleVariables { - // Use integer values if there are no visible variables, which should be a decent fallback. - b.loadInt(b.randomInt()) - } - - var properties = ["a"] - var values = [b.randomJsVariable()] - for _ in 0..<3 { - let property = chooseUniform(from: propertyNames) - guard !properties.contains(property) else { continue } - properties.append(property) - values.append(b.randomJsVariable()) - } - assert(Set(properties).count == values.count) - return (properties, values) - } - - // Temporarily overwrite the active code generators with the following generators... - let primitiveValueGenerator = ValueGenerator("PrimitiveValue") { b, n in - for _ in 0..([ - (primitiveValueGenerator, 2), - (createObjectGenerator, 1), - (objectMakerGenerator, 1), - (objectConstructorGenerator, 1), - (objectClassGenerator, 1), - - (propertyStoreGenerator, 10), - (propertyLoadGenerator, 10), - (propertyConfigureGenerator, 5), - (functionDefinitionGenerator, 2), - (functionCallGenerator, 3), - (constructorCallGenerator, 2), - (functionJitCallGenerator, 2) - ])) - - // ... run some of the ValueGenerators to create some initial objects ... - b.buildPrefix() - // ... and generate a bunch of code. - b.build(n: 100, by: .generating) - - // Now, restore the previous code generators and generate some more code. - b.fuzzer.setCodeGenerators(prevCodeGenerators) - b.build(n: 10) - - // Finally, run HeapObjectVerify on all our generated objects (that are still in scope). - for obj in b.visibleVariables where b.type(of: obj).Is(objType) { - b.eval("%HeapObjectVerify(%@)", with: [obj]) - } -} - -fileprivate let ValueSerializerFuzzer = ProgramTemplate("ValueSerializerFuzzer") { b in - b.buildPrefix() - - // Create some random values that can be serialized below. - b.build(n: 50) - - // Load necessary builtins - let d8 = b.createNamedVariable(forBuiltin: "d8") - let serializer = b.getProperty("serializer", of: d8) - let Uint8Array = b.createNamedVariable(forBuiltin: "Uint8Array") - - // Serialize a random object - let content = b.callMethod("serialize", on: serializer, withArgs: [b.randomJsVariable()]) - let u8 = b.construct(Uint8Array, withArgs: [content]) - - // Choose a random byte to change - let index = Int64.random(in: 0..<100) - - // Either flip or replace the byte - let newByte: Variable - if probability(0.5) { - let bit = b.loadInt(1 << Int.random(in: 0..<8)) - let oldByte = b.getElement(index, of: u8) - newByte = b.binary(oldByte, bit, with: .Xor) - } else { - newByte = b.loadInt(Int64.random(in: 0..<256)) - } - b.setElement(index, of: u8, to: newByte) - - // Deserialize the resulting buffer - let _ = b.callMethod("deserialize", on: serializer, withArgs: [content]) - - // Generate some more random code to (hopefully) use the deserialized objects in some interesting way. - b.build(n: 10) -} - -// This template fuzzes the RegExp engine. -// It finds bugs like: crbug.com/1437346 and crbug.com/1439691. -fileprivate let RegExpFuzzer = ProgramTemplate("RegExpFuzzer") { b in - // Taken from: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:v8/test/fuzzer/regexp-builtins.cc;l=212;drc=a61b95c63b0b75c1cfe872d9c8cdf927c226046e - let twoByteSubjectString = "f\\uD83D\\uDCA9ba\\u2603" - - let replacementCandidates = [ - "'X'", - "'$1$2$3'", - "'$$$&$`$\\'$1'", - "() => 'X'", - "(arg0, arg1, arg2, arg3, arg4) => arg0 + arg1 + arg2 + arg3 + arg4", - "() => 42" - ] - - let lastIndices = [ - "undefined", "-1", "0", - "1", "2", "3", - "4", "5", "6", - "7", "8", "9", - "50", "4294967296", "2147483647", - "2147483648", "NaN", "Not a Number" - ] - - let f = b.buildPlainFunction(with: .parameters(n: 0)) { _ in - let (pattern, flags) = b.randomRegExpPatternAndFlags() - let regExpVar = b.loadRegExp(pattern, flags) - - let lastIndex = chooseUniform(from: lastIndices) - let lastIndexString = b.loadString(lastIndex) - - b.setProperty("lastIndex", of: regExpVar, to: lastIndexString) - - let subjectVar: Variable - - if probability(0.1) { - subjectVar = b.loadString(twoByteSubjectString) - } else { - subjectVar = b.loadString(b.randomString()) - } - - let resultVar = b.loadNull() - - b.buildTryCatchFinally(tryBody: { - let symbol = b.createNamedVariable(forBuiltin: "Symbol") - withEqualProbability({ - let res = b.callMethod("exec", on: regExpVar, withArgs: [subjectVar]) - b.reassign(resultVar, to: res) - }, { - let prop = b.getProperty("match", of: symbol) - let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar]) - b.reassign(resultVar, to: res) - }, { - let prop = b.getProperty("replace", of: symbol) - let replacement = withEqualProbability({ - b.loadString(b.randomString()) - }, { - b.loadString(chooseUniform(from: replacementCandidates)) - }) - let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar, replacement]) - b.reassign(resultVar, to: res) - }, { - let prop = b.getProperty("search", of: symbol) - let res = b.callComputedMethod(prop, on: regExpVar, withArgs: [subjectVar]) - b.reassign(resultVar, to: res) - }, { - let prop = b.getProperty("split", of: symbol) - let randomSplitLimit = withEqualProbability({ - "undefined" - }, { - "'not a number'" - }, { - String(b.randomInt()) - }) - let limit = b.loadString(randomSplitLimit) - let res = b.callComputedMethod(symbol, on: regExpVar, withArgs: [subjectVar, limit]) - b.reassign(resultVar, to: res) - }, { - let res = b.callMethod("test", on: regExpVar, withArgs: [subjectVar]) - b.reassign(resultVar, to: res) - }) - }, catchBody: { _ in - }) - - b.build(n: 7) - - b.doReturn(resultVar) - } - - b.eval("%SetForceSlowPath(false)"); - // compile the regexp once - b.callFunction(f) - let resFast = b.callFunction(f) - b.eval("%SetForceSlowPath(true)"); - let resSlow = b.callFunction(f) - b.eval("%SetForceSlowPath(false)"); - - b.build(n: 15) -} - -// Emits calls with recursive calls of limited depth. -fileprivate let LazyDeoptFuzzer = ProgramTemplate("LazyDeoptFuzzer") { b in - b.buildPrefix() - b.build(n: 30) - - let counter = b.loadInt(0) - let max = b.loadInt(Int64.random(in: 2...5)) - let params = b.randomParameters() - let dummyFct = b.buildPlainFunction(with: params) { args in - b.loadString("Dummy function for emitting recursive call") - } - let realFct = b.buildPlainFunction(with: params) { args in - b.build(n: 10) - - b.buildIf(b.compare(counter, with: max, using: .lessThan)) { - b.reassign(counter, to: b.binary(counter, b.loadInt(1), with: .Add)) - b.callFunction(dummyFct, withArgs: b.randomArguments(forCalling: dummyFct)) - } - // Mark the function for deoptimization. Due to the recursive pattern above, on the outer - // stack frames this should trigger a lazy deoptimization. - b.eval("%DeoptimizeNow();"); - b.build(n: 30) - b.doReturn(b.randomJsVariable()) - } - - // Turn the call into a recursive call. - b.reassign(dummyFct, to: realFct) - let args = b.randomArguments(forCalling: realFct) - b.eval("%PrepareFunctionForOptimization(%@)", with: [realFct]); - b.callFunction(realFct, withArgs: args) - b.eval("%OptimizeFunctionOnNextCall(%@)", with: [realFct]); - // Call the function. - b.callFunction(realFct, withArgs: args) -} - -public extension ILType { - static let jsD8 = ILType.object(ofGroup: "D8", withProperties: ["test"], withMethods: []) - - static let jsD8Test = ILType.object(ofGroup: "D8Test", withProperties: ["FastCAPI"], withMethods: []) - - static let jsD8FastCAPI = ILType.object(ofGroup: "D8FastCAPI", withProperties: [], withMethods: ["throw_no_fallback", "add_32bit_int"]) - - static let jsD8FastCAPIConstructor = ILType.constructor([] => .jsD8FastCAPI) - - static let gcTypeEnum = ILType.enumeration(ofName: "gcType", withValues: ["minor", "major"]) - static let gcExecutionEnum = ILType.enumeration(ofName: "gcExecution", withValues: ["async", "sync"]) -} - -fileprivate let jsD8 = ObjectGroup(name: "D8", instanceType: .jsD8, properties: ["test" : .jsD8Test], methods: [:]) - -fileprivate let jsD8Test = ObjectGroup(name: "D8Test", instanceType: .jsD8Test, properties: ["FastCAPI": .jsD8FastCAPIConstructor], methods: [:]) - -fileprivate let jsD8FastCAPI = ObjectGroup(name: "D8FastCAPI", instanceType: .jsD8FastCAPI, properties: [:], - methods:["throw_no_fallback": [] => .integer, - "add_32bit_int": [.integer, .integer] => .integer - ]) - -fileprivate let gcOptions = ObjectGroup( - name: "GCOptions", - instanceType: .object(ofGroup: "GCOptions", withProperties: ["type", "execution"], withMethods: []), - properties: ["type": .gcTypeEnum, - "execution": .gcExecutionEnum], - methods: [:]) - -fileprivate let fastCallables : [(group: ILType, method: String)] = [ - (group: .jsD8FastCAPI, method: "throw_no_fallback"), - (group: .jsD8FastCAPI, method: "add_32bit_int"), -] - -fileprivate let WasmFastCallFuzzer = WasmProgramTemplate("WasmFastCallFuzzer") { b in - b.buildPrefix() - b.build(n: 10) - let target = fastCallables.randomElement()! - let apiObj = b.findOrGenerateType(target.group) - - // Bind the API function so that it can be called from WebAssembly. - let wrapped = b.bindMethod(target.method, on: apiObj) - - let functionSig = chooseUniform(from: b.methodSignatures(of: target.method, on: target.group)) - let wrappedSig = [.plain(b.type(of: apiObj))] + functionSig.parameters => functionSig.outputType - - let m = b.buildWasmModule { m in - let allWasmTypes: WeightedList = WeightedList([(.wasmi32, 1), (.wasmi64, 1), (.wasmf32, 1), (.wasmf64, 1), (.wasmExternRef, 1), (.wasmFuncRef, 1)]) - let wasmSignature = ProgramBuilder.convertJsSignatureToWasmSignature(wrappedSig, availableTypes: allWasmTypes) - m.addWasmFunction(with: wasmSignature) {fbuilder, _, _ in - let args = b.randomWasmArguments(forWasmSignature: wasmSignature) - if let args { - let maybeRet = fbuilder.wasmJsCall(function: wrapped, withArgs: args, withWasmSignature: wasmSignature) - if let ret = maybeRet { - return [ret] - } - } else { - logger.error("Arguments should have been generated") - } - return wasmSignature.outputTypes.map(fbuilder.findOrGenerateWasmVar) - } - } - - let exports = m.loadExports() - - for (methodName, _) in m.getExportedMethods() { - let exportedMethod = b.getProperty(methodName, of: exports) - b.eval("%WasmTierUpFunction(%@)", with: [exportedMethod]) - let args = b.findOrGenerateArguments(forSignature: wrappedSig) - b.callMethod(methodName, on: exports, withArgs: args) - } -} - -fileprivate let FastApiCallFuzzer = ProgramTemplate("FastApiCallFuzzer") { b in - b.buildPrefix() - b.build(n: 20) - let parameterCount = probability(0.5) ? 0 : Int.random(in: 1...4) - - let f = b.buildPlainFunction(with: .parameters(n: parameterCount)) { args in - b.build(n: 10) - let target = fastCallables.randomElement()! - let apiObj = b.findOrGenerateType(target.group) - let functionSig = chooseUniform(from: b.methodSignatures(of: target.method, on: target.group)) - let apiCall = b.callMethod(target.method, on: apiObj, withArgs: b.findOrGenerateArguments(forSignature: functionSig), guard: true) - b.doReturn(apiCall) - } - - let args = b.randomJsVariables(n: Int.random(in: 0...5)) - b.callFunction(f, withArgs: args) - - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - - b.callFunction(f, withArgs: args) - b.callFunction(f, withArgs: args) - - b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); - - b.callFunction(f, withArgs: args) - - b.build(n: 10) -} let v8Profile = Profile( processArgs: { randomize in @@ -778,7 +241,7 @@ let v8Profile = Profile( (TurbofanVerifyTypeGenerator, 10), (WorkerGenerator, 10), - (GcGenerator, 10), + (V8GcGenerator, 10), (WasmStructGenerator, 15), (WasmArrayGenerator, 15), @@ -787,7 +250,7 @@ let v8Profile = Profile( additionalProgramTemplates: WeightedList([ (MapTransitionFuzzer, 1), (ValueSerializerFuzzer, 1), - (RegExpFuzzer, 1), + (V8RegExpFuzzer, 1), (WasmFastCallFuzzer, 1), (FastApiCallFuzzer, 1), (LazyDeoptFuzzer, 1), diff --git a/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift b/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift index d276a96a0..e06a530d7 100644 --- a/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift +++ b/Sources/FuzzilliCli/Profiles/V8SandboxProfile.swift @@ -14,63 +14,6 @@ import Fuzzilli -// TODO: move common parts (e.g. generators) into a V8CommonProfile.swift. - -fileprivate let ForceJITCompilationThroughLoopGenerator = CodeGenerator("ForceJITCompilationThroughLoopGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.buildRepeatLoop(n: 100) { _ in - b.callFunction(f, withArgs: arguments) - } -} - -fileprivate let ForceTurboFanCompilationGenerator = CodeGenerator("ForceTurboFanCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.callFunction(f, withArgs: arguments) - - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - - b.eval("%OptimizeFunctionOnNextCall(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) -} - -fileprivate let ForceMaglevCompilationGenerator = CodeGenerator("ForceMaglevCompilationGenerator", inputs: .required(.function())) { b, f in - assert(b.type(of: f).Is(.function())) - let arguments = b.randomArguments(forCalling: f) - - b.callFunction(f, withArgs: arguments) - - b.eval("%PrepareFunctionForOptimization(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) - b.callFunction(f, withArgs: arguments) - - b.eval("%OptimizeMaglevOnNextCall(%@)", with: [f]); - - b.callFunction(f, withArgs: arguments) -} - -// Insert random GC calls throughout our code. -fileprivate let GcGenerator = CodeGenerator("GcGenerator") { b in - let gc = b.createNamedVariable(forBuiltin: "gc") - - // Do minor GCs more frequently. - let type = b.loadString(probability(0.25) ? "major" : "minor") - // If the execution type is 'async', gc() returns a Promise, we currently - // do not really handle other than typing the return of gc to .undefined | - // .jsPromise. One could either chain a .then or create two wrapper - // functions that are differently typed such that fuzzilli always knows - // what the type of the return value is. - let execution = b.loadString(probability(0.5) ? "sync" : "async") - b.callFunction(gc, withArgs: [b.createObject(with: ["type": type, "execution": execution])]) -} // A post-processor that inserts calls to the `corrupt` function (defined in the prefix below) into the generated samples. fileprivate struct SandboxFuzzingPostProcessor: FuzzingPostProcessor { @@ -539,7 +482,7 @@ let v8SandboxProfile = Profile( (ForceJITCompilationThroughLoopGenerator, 5), (ForceTurboFanCompilationGenerator, 5), (ForceMaglevCompilationGenerator, 5), - (GcGenerator, 10), + (V8GcSandboxGenerator, 10), ], additionalProgramTemplates: WeightedList([