1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Description of this file. 7 */ 8 9'use strict'; 10 11const assert = require('assert'); 12const fs = require('fs'); 13const path = require('path'); 14 15const program = require('commander'); 16 17const corpus = require('./corpus.js'); 18const differentialScriptMutator = require('./differential_script_mutator.js'); 19const random = require('./random.js'); 20const scriptMutator = require('./script_mutator.js'); 21const sourceHelpers = require('./source_helpers.js'); 22 23// Maximum number of test inputs to use for one fuzz test. 24const MAX_TEST_INPUTS_PER_TEST = 10; 25 26// Base implementations for default or differential fuzzing. 27const SCRIPT_MUTATORS = { 28 default: scriptMutator.ScriptMutator, 29 foozzie: differentialScriptMutator.DifferentialScriptMutator, 30}; 31 32function getRandomInputs(primaryCorpus, secondaryCorpora, count) { 33 count = random.randInt(2, count); 34 35 // Choose 40%-80% of inputs from primary corpus. 36 const primaryCount = Math.floor(random.uniform(0.4, 0.8) * count); 37 count -= primaryCount; 38 39 let inputs = primaryCorpus.getRandomTestcases(primaryCount); 40 41 // Split remainder equally between the secondary corpora. 42 const secondaryCount = Math.floor(count / secondaryCorpora.length); 43 44 for (let i = 0; i < secondaryCorpora.length; i++) { 45 let currentCount = secondaryCount; 46 if (i == secondaryCorpora.length - 1) { 47 // Last one takes the remainder. 48 currentCount = count; 49 } 50 51 count -= currentCount; 52 if (currentCount) { 53 inputs = inputs.concat( 54 secondaryCorpora[i].getRandomTestcases(currentCount)); 55 } 56 } 57 58 return random.shuffle(inputs); 59} 60 61function collect(value, total) { 62 total.push(value); 63 return total; 64} 65 66function overrideSettings(settings, settingOverrides) { 67 for (const setting of settingOverrides) { 68 const parts = setting.split('='); 69 settings[parts[0]] = parseFloat(parts[1]); 70 } 71} 72 73function* randomInputGen(engine) { 74 const inputDir = path.resolve(program.input_dir); 75 76 const v8Corpus = new corpus.Corpus(inputDir, 'v8'); 77 const chakraCorpus = new corpus.Corpus(inputDir, 'chakra'); 78 const spiderMonkeyCorpus = new corpus.Corpus(inputDir, 'spidermonkey'); 79 const jscCorpus = new corpus.Corpus(inputDir, 'WebKit/JSTests'); 80 const crashTestsCorpus = new corpus.Corpus(inputDir, 'CrashTests'); 81 82 for (let i = 0; i < program.no_of_files; i++) { 83 let inputs; 84 if (engine === 'V8') { 85 inputs = getRandomInputs( 86 v8Corpus, 87 random.shuffle([chakraCorpus, spiderMonkeyCorpus, jscCorpus, 88 crashTestsCorpus, v8Corpus]), 89 MAX_TEST_INPUTS_PER_TEST); 90 } else if (engine == 'chakra') { 91 inputs = getRandomInputs( 92 chakraCorpus, 93 random.shuffle([v8Corpus, spiderMonkeyCorpus, jscCorpus, 94 crashTestsCorpus]), 95 MAX_TEST_INPUTS_PER_TEST); 96 } else if (engine == 'spidermonkey') { 97 inputs = getRandomInputs( 98 spiderMonkeyCorpus, 99 random.shuffle([v8Corpus, chakraCorpus, jscCorpus, 100 crashTestsCorpus]), 101 MAX_TEST_INPUTS_PER_TEST); 102 } else { 103 inputs = getRandomInputs( 104 jscCorpus, 105 random.shuffle([chakraCorpus, spiderMonkeyCorpus, v8Corpus, 106 crashTestsCorpus]), 107 MAX_TEST_INPUTS_PER_TEST); 108 } 109 110 if (inputs.length > 0) { 111 yield inputs; 112 } 113 } 114} 115 116function* corpusInputGen() { 117 const inputCorpus = new corpus.Corpus( 118 path.resolve(program.input_dir), 119 program.mutate_corpus, 120 program.extra_strict); 121 for (const input of inputCorpus.getAllTestcases()) { 122 yield [input]; 123 } 124} 125 126function* enumerate(iterable) { 127 let i = 0; 128 for (const value of iterable) { 129 yield [i, value]; 130 i++; 131 } 132} 133 134function main() { 135 Error.stackTraceLimit = Infinity; 136 137 program 138 .version('0.0.1') 139 .option('-i, --input_dir <path>', 'Input directory.') 140 .option('-o, --output_dir <path>', 'Output directory.') 141 .option('-n, --no_of_files <n>', 'Output directory.', parseInt) 142 .option('-c, --mutate_corpus <name>', 'Mutate single files in a corpus.') 143 .option('-e, --extra_strict', 'Additionally parse files in strict mode.') 144 .option('-m, --mutate <path>', 'Mutate a file and output results.') 145 .option('-s, --setting [setting]', 'Settings overrides.', collect, []) 146 .option('-v, --verbose', 'More verbose printing.') 147 .option('-z, --zero_settings', 'Zero all settings.') 148 .parse(process.argv); 149 150 const settings = scriptMutator.defaultSettings(); 151 if (program.zero_settings) { 152 for (const key of Object.keys(settings)) { 153 settings[key] = 0.0; 154 } 155 } 156 157 if (program.setting.length > 0) { 158 overrideSettings(settings, program.setting); 159 } 160 161 let app_name = process.env.APP_NAME; 162 if (app_name && app_name.endsWith('.exe')) { 163 app_name = app_name.substr(0, app_name.length - 4); 164 } 165 166 if (app_name === 'd8' || 167 app_name === 'v8_simple_inspector_fuzzer' || 168 app_name === 'v8_foozzie.py') { 169 // V8 supports running the raw d8 executable, the inspector fuzzer or 170 // the differential fuzzing harness 'foozzie'. 171 settings.engine = 'V8'; 172 } else if (app_name === 'ch') { 173 settings.engine = 'chakra'; 174 } else if (app_name === 'js') { 175 settings.engine = 'spidermonkey'; 176 } else if (app_name === 'jsc') { 177 settings.engine = 'jsc'; 178 } else { 179 console.log('ERROR: Invalid APP_NAME'); 180 process.exit(1); 181 } 182 183 const mode = process.env.FUZZ_MODE || 'default'; 184 assert(mode in SCRIPT_MUTATORS, `Unknown mode ${mode}`); 185 const mutator = new SCRIPT_MUTATORS[mode](settings); 186 187 if (program.mutate) { 188 const absPath = path.resolve(program.mutate); 189 const baseDir = path.dirname(absPath); 190 const fileName = path.basename(absPath); 191 const input = sourceHelpers.loadSource( 192 baseDir, fileName, program.extra_strict); 193 const mutated = mutator.mutateMultiple([input]); 194 console.log(mutated.code); 195 return; 196 } 197 198 let inputGen; 199 200 if (program.mutate_corpus) { 201 inputGen = corpusInputGen(); 202 } else { 203 inputGen = randomInputGen(settings.engine); 204 } 205 206 for (const [i, inputs] of enumerate(inputGen)) { 207 const outputPath = path.join(program.output_dir, 'fuzz-' + i + '.js'); 208 209 const start = Date.now(); 210 const paths = inputs.map(input => input.relPath); 211 212 try { 213 const mutated = mutator.mutateMultiple(inputs); 214 fs.writeFileSync(outputPath, mutated.code); 215 216 if (settings.engine === 'V8' && mutated.flags && mutated.flags.length > 0) { 217 const flagsPath = path.join(program.output_dir, 'flags-' + i + '.js'); 218 fs.writeFileSync(flagsPath, mutated.flags.join(' ')); 219 } 220 } catch (e) { 221 if (e.message.startsWith('ENOSPC')) { 222 console.log('ERROR: No space left. Bailing out...'); 223 console.log(e); 224 return; 225 } 226 console.log(`ERROR: Exception during mutate: ${paths}`); 227 console.log(e); 228 continue; 229 } finally { 230 if (program.verbose) { 231 const duration = Date.now() - start; 232 console.log(`Mutating ${paths} took ${duration} ms.`); 233 } 234 } 235 if ((i + 1) % 10 == 0) { 236 console.log('Up to ', i + 1); 237 } 238 } 239} 240 241main(); 242