function main() { // If a sample with this instrumentation crashes, it may need the `fuzzilli` function to reproduce the crash. if (typeof fuzzilli === 'undefined') fuzzilli = function() {}; const Probe = (function() { // // "Import" the common runtime-assisted mutator code. This will make various utility functions available. // // Note: runtime instrumentation code must generally assume that any operation performed on any object coming from the "outside", may raise an exception, for example due to triggering a Proxy trap. // Further, it must also assume that the environment has been modified arbitrarily. For example, the Array.prototype[@@iterator] may have been set to an invalid value, so using `for...of` syntax could trigger an exception. // Load all necessary routines and objects into local variables as they may be overwritten by the program. // We generally want to avoid triggerring observable side-effects, such as storing or loading // properties. For that reason, we prefer to use builtins like Object.defineProperty. const ProxyConstructor = Proxy; const BigIntConstructor = BigInt; const SetConstructor = Set; const ObjectPrototype = Object.prototype; const getOwnPropertyNames = Object.getOwnPropertyNames; const getPrototypeOf = Object.getPrototypeOf; const setPrototypeOf = Object.setPrototypeOf; const stringify = JSON.stringify; const hasOwnProperty = Object.hasOwn; const defineProperty = Object.defineProperty; const propertyValues = Object.values; const parseInteger = parseInt; const NumberIsInteger = Number.isInteger; const isNaN = Number.isNaN; const isFinite = Number.isFinite; const truncate = Math.trunc; const apply = Reflect.apply; const construct = Reflect.construct; const ReflectGet = Reflect.get; const ReflectSet = Reflect.set; const ReflectHas = Reflect.has; // Bind methods to local variables. These all expect the 'this' object as first parameter. const concat = Function.prototype.call.bind(Array.prototype.concat); const findIndex = Function.prototype.call.bind(Array.prototype.findIndex); const includes = Function.prototype.call.bind(Array.prototype.includes); const shift = Function.prototype.call.bind(Array.prototype.shift); const pop = Function.prototype.call.bind(Array.prototype.pop); const push = Function.prototype.call.bind(Array.prototype.push); const filter = Function.prototype.call.bind(Array.prototype.filter); const execRegExp = Function.prototype.call.bind(RegExp.prototype.exec); const stringSlice = Function.prototype.call.bind(String.prototype.slice); const toUpperCase = Function.prototype.call.bind(String.prototype.toUpperCase); const numberToString = Function.prototype.call.bind(Number.prototype.toString); const bigintToString = Function.prototype.call.bind(BigInt.prototype.toString); const stringStartsWith = Function.prototype.call.bind(String.prototype.startsWith); const setAdd = Function.prototype.call.bind(Set.prototype.add); const setHas = Function.prototype.call.bind(Set.prototype.has); const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER; const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; // Simple, seedable PRNG based on a LCG. class RNG { m = 2 ** 32; a = 1664525; c = 1013904223; x; constructor(seed) { this.x = seed; } randomInt() { this.x = (this.x * this.a + this.c) % this.m; if (!isInteger(this.x)) throw "RNG state is not an Integer!" return this.x; } randomFloat() { return this.randomInt() / this.m; } probability(p) { return this.randomFloat() < p; } reseed(seed) { this.x = seed; } } // When creating empty arrays to which elements are later added, use a custom array type that has a null prototype. This way, the arrays are not // affected by changes to the Array.prototype that could interfere with array builtins (e.g. indexed setters or a modified .constructor property). function EmptyArray() { let array = []; setPrototypeOf(array, null); return array; } // // Misc. helper functions. // // Type check helpers. These are less error-prone than manually using typeof and comparing against a string. function isObject(v) { return typeof v === 'object'; } function isFunction(v) { return typeof v === 'function'; } function isString(v) { return typeof v === 'string'; } function isNumber(v) { return typeof v === 'number'; } function isBigint(v) { return typeof v === 'bigint'; } function isSymbol(v) { return typeof v === 'symbol'; } function isBoolean(v) { return typeof v === 'boolean'; } function isUndefined(v) { return typeof v === 'undefined'; } // Helper function to determine if a value is an integer, and within [MIN_SAFE_INTEGER, MAX_SAFE_INTEGER]. function isInteger(n) { return isNumber(n) && NumberIsInteger(n) && n>= MIN_SAFE_INTEGER && n <= MAX_SAFE_INTEGER; } // Helper function to determine if a string is "simple". We only include simple strings for property/method names or string literals. // A simple string is basically a valid, property name with a maximum length. const simpleStringRegExp = /^[0-9a-zA-Z_$]+$/; function isSimpleString(s) { if (!isString(s)) throw "Non-string argument to isSimpleString: " + s; return s.length < 50 && execRegExp(simpleStringRegExp, s) !== null; } // Helper function to determine if a string is numeric and its numeric value representable as an integer. function isNumericString(s) { if (!isString(s)) return false; let number = parseInteger(s); return number >= MIN_SAFE_INTEGER && number <= MAX_SAFE_INTEGER && numberToString(number) === s; } // Helper function to determine whether a property can be accessed without raising an exception. function tryAccessProperty(prop, obj) { try { obj[prop]; return true; } catch (e) { return false; } } // Helper function to determine if a property exists on an object or one of its prototypes. If an exception is raised, false is returned. function tryHasProperty(prop, obj) { try { return prop in obj; } catch (e) { return false; } } // Helper function to load a property from an object. If an exception is raised, undefined is returned. function tryGetProperty(prop, obj) { try { return obj[prop]; } catch (e) { return undefined; } } // Helper function to obtain the own properties of an object. If that raises an exception (e.g. on a Proxy object), an empty array is returned. function tryGetOwnPropertyNames(obj) { try { return getOwnPropertyNames(obj); } catch (e) { return new Array(); } } // Helper function to fetch the prototype of an object. If that raises an exception (e.g. on a Proxy object), null is returned. function tryGetPrototypeOf(obj) { try { return getPrototypeOf(obj); } catch (e) { return null; } } // Helper function to that creates a wrapper function for the given function which will call it in a try-catch and return false on exception. function wrapInTryCatch(f) { return function() { try { return apply(f, this, arguments); } catch (e) { return false; } }; } // // Basic random number generation utility functions. // // Initially the rng is seeded randomly, specific mutators can reseed() the rng if they need deterministic behavior. // See the explore operation in JsOperations.swift for an example. let rng = new RNG(truncate(Math.random() * 2**32)); function probability(p) { if (p < 0 || p > 1) throw "Argument to probability must be a number between zero and one"; return rng.probability(p); } function randomIntBetween(start, end) { if (!isInteger(start) || !isInteger(end)) throw "Arguments to randomIntBetween must be integers"; return (rng.randomInt() % (end - start)) + start; } function randomFloat() { return rng.randomFloat(); } function randomBigintBetween(start, end) { if (!isBigint(start) || !isBigint(end)) throw "Arguments to randomBigintBetween must be bigints"; if (!isInteger(Number(start)) || !isInteger(Number(end))) throw "Arguments to randomBigintBetween must be representable as regular intergers"; return BigIntConstructor(randomIntBetween(Number(start), Number(end))); } function randomIntBelow(n) { if (!isInteger(n)) throw "Argument to randomIntBelow must be an integer"; return rng.randomInt() % n; } function randomElement(array) { return array[randomIntBelow(array.length)]; } // Action constants. const PROPERTY_LOAD = "loads"; const PROPERTY_STORE = "stores"; // Property access outcomes. const PROPERTY_NOT_FOUND = 0; const PROPERTY_FOUND = 1; // // Result recording and reporting. // let results = { __proto__: null }; function reportError(msg) { fuzzilli('FUZZILLI_PRINT', 'PROBING_ERROR: ' + msg); } function reportResults() { fuzzilli('FUZZILLI_PRINT', 'PROBING_RESULTS: ' + stringify(results)); } // Record a property action performed on a probe. // |target| is expected to be the original prototype of the probe object. It is used to determine whether the accessed property exists anywhere in the prototype chain of the probe. function recordAction(action, id, target, key) { let outcome = PROPERTY_NOT_FOUND; if (ReflectHas(target, key)) { outcome = PROPERTY_FOUND; } let keyString = key; if (typeof keyString !== 'string') { try { keyString = key.toString(); if (typeof keyString !== 'string') throw 'not a string'; } catch(e) { // Got some "weird" property key. Ignore it. return; } } if (!isSimpleString(keyString) && !isNumericString(keyString) && !isSymbol(key)) { // Cannot deal with this property name. Ignore it. return; } if (isSymbol(key) && !stringStartsWith(keyString, 'Symbol(Symbol.')) { // We can only deal with well-known symbols (e.g. "Symbol(Symbol.toPrimitive)"), and this isn't one. Ignore it. return; } if (!hasOwnProperty(results, id)) { results[id] = { [PROPERTY_LOAD]: { __proto__: null }, [PROPERTY_STORE]: { __proto__: null } }; } // If the same action is performed on the same probe multiple times, we keep the last result. results[id][action][keyString] = outcome; } function recordActionWithErrorHandling(action, id, target, key) { try { recordAction(action, id, target, key); } catch(e) { reportError(e); } } // // Probe implementation. // function probe(id, value) { let originalPrototype, newPrototype; let handler = { get(target, key, receiver) { // Special logic to deal with programs that fetch the prototype of an object after it was turned into a probe. // In that case, the probe Proxy would leak to the script, potentially causing incorrect behaviour. To deal with that, // we (1) return the original prototype when __proto__ is loaded (but this can be "bypassed" through Object.getPrototypeOf) // and (2) attempt to detect property accesses on the prototype itself (instead of on the probe) and handle those separately. if (key === '__proto__' && receiver === value) return originalPrototype; if (receiver === newPrototype) return ReflectGet(target, key); recordActionWithErrorHandling(PROPERTY_LOAD, id, target, key); return ReflectGet(target, key, receiver); }, set(target, key, value, receiver) { if (receiver === newPrototype) return ReflectSet(target, key, value); recordActionWithErrorHandling(PROPERTY_STORE, id, target, key); return ReflectSet(target, key, value, receiver); }, has(target, key) { // Treat this as a load. recordActionWithErrorHandling(PROPERTY_LOAD, id, target, key); return ReflectHas(target, key); }, }; try { // This can fail, e.g. due to "Cannot convert undefined or null to object" or if the object is non-extensible. In that case, do nothing. originalPrototype = getPrototypeOf(value); newPrototype = new ProxyConstructor(originalPrototype, handler); setPrototypeOf(value, newPrototype); } catch (e) {} } function probeWithErrorHandling(id, value) { try { probe(id, value); } catch(e) { reportError(e); } } return { probe: probeWithErrorHandling, reportResults: reportResults }; })(); Probe.probe("v1", "p"); const v3 = [39193,7,127,32909,-710089876,-36042,65535,-181762991,53525]; try { v3.findLast("p"); } catch (e) {} const v5 = [127,-128]; Probe.probe("v5", v5); let v6; try { v6 = v5.toReversed(); } catch (e) {} Probe.probe("v6", v6); const v7 = [512,52666]; Probe.probe("v8", Date); const v9 = new Date(); function f10(a11, a12, a13, a14) { Probe.probe("v11", a11); Probe.probe("v12", a12); Probe.probe("v14", a14); const v15 = a12?.constructor; Probe.probe("v15", v15); try { new v15(v3); } catch (e) {} const v17 = a11?.constructor; const v18 = v17.length; v18 - v18; try { new v17(a11); } catch (e) {} try { a14.entries(); } catch (e) {} const o30 = { ...a11, ...a13, get d() { Probe.probe("v23", ArrayBuffer); Probe.probe("v24", 3485); const o25 = { "maxByteLength": 3485, }; const v27 = new ArrayBuffer(3485, o25); Probe.probe("v28", Uint8Array); new Uint8Array(v27); return a11; }, }; o30[1]; return o30; } Probe.probe("v10", f10); let v32; try { v32 = f10("vlO6u", Date, "vlO6u", v9); } catch (e) {} const v33 = v32[2]; try { v33.trimStart(); } catch (e) {} f10(v5, v7, v7, v7); const v36 = f10(v7, v3, v7, v5); Probe.probe("v36", v36); const v37 = v36?.__lookupGetter__; try { new v37(v6); } catch (e) {} f10(v5, v3, v5, v5); function F40() { if (!new.target) { throw 'must be called with new'; } Probe.probe("v41", this); const v42 = this?.constructor; Probe.probe("v42", v42); try { new v42(); } catch (e) {} } class C44 extends F40 { } C44.name = C44; true || true; !true; const o48 = { }; Probe.probe("v48", o48); o48.e = o48; o48.e = o48; Proxy.name; try { new Proxy("vlO6u", v36); } catch (e) {} const v52 = new Proxy(C44, o48); Probe.probe("v52", v52); let v53; try { v53 = new v52(); } catch (e) {} Probe.probe("v53", v53); v53.c = v53; v52.name = v52; let v54 = v52 % true; let v55 = v54--; Probe.probe("v55", v55); v55--; Probe.reportResults(); } main(); // CRASH INFO // ========== // TERMSIG: 11 // STDERR: // // STDOUT: // // FUZZER ARGS: ./FuzzilliCli --profile=serenity --storagePath=fuzzilli-storage --jobs=12 --exportStatistics ./bin/FuzzilliJs // TARGET ARGS: ./bin/FuzzilliJs // CONTRIBUTORS: // EXECUTION TIME: 574ms