Home

Awesome

js-fuzz

This is still very much a work in progress and is probably not suitable for "real" use yet!

js-fuzz is an American Fuzzy Lop-inspired fuzz tester for JavaScript code. It provides coverage-driven analysis and minimization while being fast and extraordinarily simple to use.

Example

This program tests that all valid JSON strings can be parsed with JSON5:

const JSON5 = require('json5')

exports.fuzz = input => {
  input = input.toString('utf8') // we give you buffers by default

  let isPlainJSON = true
  let isJSON5 = true
  try { JSON.parse(input) } catch () { isPlainJSON = false }
  try { JSON5.parse(input) } catch () { isJSON5 = false }

  // We catch and thrown errors and mark them as failures
  if (isPlainJSON && !isJSON5) {
    throw new Error('Found a string that was JSON but not JSON5');
  }

  return isPlainJSON ? 1 : 0
}

Usage

js-fuzz myFile.js

Usage is quite similar to go-fuzz. You should give the js-fuzz tool the path to a module that exports a fuzz function. This function will be called with an input Buffer, and should return:

In the above example, we asked to increase the priority of strings that can be parsed as plain JSON, since we want more of that sort of thing in order to test against JSON5. You can also return Promises from the fuzz function, or take a callback.

exports.fuzz = input => {
  return doSomethingAsync(input)
    .then(out => anotherThing())
    .then(valid => valid ? 1 : 0)
}

// or

exports.fuzz = (input, callback) => {
  myNodeStyleThing(input, err => {
    if (err) {
      callback(err)
    } else {
      callback(null, 1)
    }
  })
}

The fuzzer will run until you terminate it, reporting stats in your terminal, and will write output in the form of text files into a fuzz-output directory in your current working directory. You'll probably be most interested in ./fuzz-output/crashers, which will contain all inputs that caused an error in your program!

js-fuzz does assume that your program's behaviour is deterministic and that the fuzz function is pure. It will work when this does not hold true, but it will be less efficient at discovering code paths and edge cases.

Internals

The runner spawns child processes which execute the given input module, by default spawning one child per CPU core. We use Esprima and Escodegen to provide branch coverage on JavaScript code using a require hook. The coverage that's injected has little performance impact; running a benchmark on Douglas Crockford's JSON implementation, it's about a 0.6% overhead. Most of the mechanics are based heavily on the AFL whitepaper.

Limitations

Most limitations revolve around what Node will let us get at without leaning on native extensions, which I would prefer to avoid for maintainability and compatibility reasons.

Acknowledgments