| // Copyright 2020 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Description of this file. |
| */ |
| |
| 'use strict'; |
| |
| const assert = require('assert'); |
| const fs = require('fs'); |
| const path = require('path'); |
| |
| const program = require('commander'); |
| |
| const corpus = require('./corpus.js'); |
| const differentialScriptMutator = require('./differential_script_mutator.js'); |
| const random = require('./random.js'); |
| const scriptMutator = require('./script_mutator.js'); |
| const sourceHelpers = require('./source_helpers.js'); |
| |
| // Maximum number of test inputs to use for one fuzz test. |
| const MAX_TEST_INPUTS_PER_TEST = 10; |
| |
| // Base implementations for default or differential fuzzing. |
| const SCRIPT_MUTATORS = { |
| default: scriptMutator.ScriptMutator, |
| foozzie: differentialScriptMutator.DifferentialScriptMutator, |
| }; |
| |
| function getRandomInputs(primaryCorpus, secondaryCorpora, count) { |
| count = random.randInt(2, count); |
| |
| // Choose 40%-80% of inputs from primary corpus. |
| const primaryCount = Math.floor(random.uniform(0.4, 0.8) * count); |
| count -= primaryCount; |
| |
| let inputs = primaryCorpus.getRandomTestcases(primaryCount); |
| |
| // Split remainder equally between the secondary corpora. |
| const secondaryCount = Math.floor(count / secondaryCorpora.length); |
| |
| for (let i = 0; i < secondaryCorpora.length; i++) { |
| let currentCount = secondaryCount; |
| if (i == secondaryCorpora.length - 1) { |
| // Last one takes the remainder. |
| currentCount = count; |
| } |
| |
| count -= currentCount; |
| if (currentCount) { |
| inputs = inputs.concat( |
| secondaryCorpora[i].getRandomTestcases(currentCount)); |
| } |
| } |
| |
| return random.shuffle(inputs); |
| } |
| |
| function collect(value, total) { |
| total.push(value); |
| return total; |
| } |
| |
| function overrideSettings(settings, settingOverrides) { |
| for (const setting of settingOverrides) { |
| const parts = setting.split('='); |
| settings[parts[0]] = parseFloat(parts[1]); |
| } |
| } |
| |
| function* randomInputGen(engine) { |
| const inputDir = path.resolve(program.input_dir); |
| |
| const v8Corpus = new corpus.Corpus(inputDir, 'v8'); |
| const chakraCorpus = new corpus.Corpus(inputDir, 'chakra'); |
| const spiderMonkeyCorpus = new corpus.Corpus(inputDir, 'spidermonkey'); |
| const jscCorpus = new corpus.Corpus(inputDir, 'WebKit/JSTests'); |
| const crashTestsCorpus = new corpus.Corpus(inputDir, 'CrashTests'); |
| |
| for (let i = 0; i < program.no_of_files; i++) { |
| let inputs; |
| if (engine === 'V8') { |
| inputs = getRandomInputs( |
| v8Corpus, |
| random.shuffle([chakraCorpus, spiderMonkeyCorpus, jscCorpus, |
| crashTestsCorpus, v8Corpus]), |
| MAX_TEST_INPUTS_PER_TEST); |
| } else if (engine == 'chakra') { |
| inputs = getRandomInputs( |
| chakraCorpus, |
| random.shuffle([v8Corpus, spiderMonkeyCorpus, jscCorpus, |
| crashTestsCorpus]), |
| MAX_TEST_INPUTS_PER_TEST); |
| } else if (engine == 'spidermonkey') { |
| inputs = getRandomInputs( |
| spiderMonkeyCorpus, |
| random.shuffle([v8Corpus, chakraCorpus, jscCorpus, |
| crashTestsCorpus]), |
| MAX_TEST_INPUTS_PER_TEST); |
| } else { |
| inputs = getRandomInputs( |
| jscCorpus, |
| random.shuffle([chakraCorpus, spiderMonkeyCorpus, v8Corpus, |
| crashTestsCorpus]), |
| MAX_TEST_INPUTS_PER_TEST); |
| } |
| |
| if (inputs.length > 0) { |
| yield inputs; |
| } |
| } |
| } |
| |
| function* corpusInputGen() { |
| const inputCorpus = new corpus.Corpus( |
| path.resolve(program.input_dir), |
| program.mutate_corpus, |
| program.extra_strict); |
| for (const input of inputCorpus.getAllTestcases()) { |
| yield [input]; |
| } |
| } |
| |
| function* enumerate(iterable) { |
| let i = 0; |
| for (const value of iterable) { |
| yield [i, value]; |
| i++; |
| } |
| } |
| |
| function main() { |
| Error.stackTraceLimit = Infinity; |
| |
| program |
| .version('0.0.1') |
| .option('-i, --input_dir <path>', 'Input directory.') |
| .option('-o, --output_dir <path>', 'Output directory.') |
| .option('-n, --no_of_files <n>', 'Output directory.', parseInt) |
| .option('-c, --mutate_corpus <name>', 'Mutate single files in a corpus.') |
| .option('-e, --extra_strict', 'Additionally parse files in strict mode.') |
| .option('-m, --mutate <path>', 'Mutate a file and output results.') |
| .option('-s, --setting [setting]', 'Settings overrides.', collect, []) |
| .option('-v, --verbose', 'More verbose printing.') |
| .option('-z, --zero_settings', 'Zero all settings.') |
| .parse(process.argv); |
| |
| const settings = scriptMutator.defaultSettings(); |
| if (program.zero_settings) { |
| for (const key of Object.keys(settings)) { |
| settings[key] = 0.0; |
| } |
| } |
| |
| if (program.setting.length > 0) { |
| overrideSettings(settings, program.setting); |
| } |
| |
| let app_name = process.env.APP_NAME; |
| if (app_name && app_name.endsWith('.exe')) { |
| app_name = app_name.substr(0, app_name.length - 4); |
| } |
| |
| if (app_name === 'd8' || app_name === 'v8_foozzie.py') { |
| // V8 supports running the raw d8 executable or the differential fuzzing |
| // harness 'foozzie'. |
| settings.engine = 'V8'; |
| } else if (app_name === 'ch') { |
| settings.engine = 'chakra'; |
| } else if (app_name === 'js') { |
| settings.engine = 'spidermonkey'; |
| } else if (app_name === 'jsc') { |
| settings.engine = 'jsc'; |
| } else { |
| console.log('ERROR: Invalid APP_NAME'); |
| process.exit(1); |
| } |
| |
| const mode = process.env.FUZZ_MODE || 'default'; |
| assert(mode in SCRIPT_MUTATORS, `Unknown mode ${mode}`); |
| const mutator = new SCRIPT_MUTATORS[mode](settings); |
| |
| if (program.mutate) { |
| const absPath = path.resolve(program.mutate); |
| const baseDir = path.dirname(absPath); |
| const fileName = path.basename(absPath); |
| const input = sourceHelpers.loadSource( |
| baseDir, fileName, program.extra_strict); |
| const mutated = mutator.mutateMultiple([input]); |
| console.log(mutated.code); |
| return; |
| } |
| |
| let inputGen; |
| |
| if (program.mutate_corpus) { |
| inputGen = corpusInputGen(); |
| } else { |
| inputGen = randomInputGen(settings.engine); |
| } |
| |
| for (const [i, inputs] of enumerate(inputGen)) { |
| const outputPath = path.join(program.output_dir, 'fuzz-' + i + '.js'); |
| |
| const start = Date.now(); |
| const paths = inputs.map(input => input.relPath); |
| |
| try { |
| const mutated = mutator.mutateMultiple(inputs); |
| fs.writeFileSync(outputPath, mutated.code); |
| |
| if (settings.engine === 'V8' && mutated.flags && mutated.flags.length > 0) { |
| const flagsPath = path.join(program.output_dir, 'flags-' + i + '.js'); |
| fs.writeFileSync(flagsPath, mutated.flags.join(' ')); |
| } |
| } catch (e) { |
| if (e.message.startsWith('ENOSPC')) { |
| console.log('ERROR: No space left. Bailing out...'); |
| console.log(e); |
| return; |
| } |
| console.log(`ERROR: Exception during mutate: ${paths}`); |
| console.log(e); |
| continue; |
| } finally { |
| if (program.verbose) { |
| const duration = Date.now() - start; |
| console.log(`Mutating ${paths} took ${duration} ms.`); |
| } |
| } |
| if ((i + 1) % 10 == 0) { |
| console.log('Up to ', i + 1); |
| } |
| } |
| } |
| |
| main(); |