Skip to content

Latest commit

 

History

History
223 lines (166 loc) · 11.5 KB

fuzz-targets.md

File metadata and controls

223 lines (166 loc) · 11.5 KB

Fuzzing using fuzz targets and the CLI

Creating fuzz targets and executing those via CLI commands is straightforward and similar to what you would expect from other fuzzers. How to do so is described in detail in the following sections.

Setting up Jazzer.js

Before you can use Jazzer.js, you have to add the required dependency @jazzer.js/core to your project. To do so, execute the following command in your project root directory.

npm install --save-dev @jazzer.js/core

This will install Jazzer.js and all required dependencies in your project.

Creating a fuzz target

Jazzer.js requires an entry point for the fuzzer, this is commonly referred to as fuzz target. A simple example is shown below.

module.exports.fuzz = function (data) {
	myAwesomeCode(data.toString());
};

A fuzz target module needs to export a function called fuzz, which takes a Buffer parameter and executes the actual code under test.

The Buffer, a subclass of Uint8Array, can be used to create needed parameters for the actual code under test. However, Buffer is not the nicest abstraction to work with. For that reason, Jazzer.js provides the wrapper class FuzzedDataProvider, which allows reading primitive types from the Buffer. An example on how to use the fuzzer input with the FuzzedDataProvider class is shown below.

const { FuzzedDataProvider } = require("@jazzer.js/core");

module.exports.fuzz = function (fuzzerInputData) {
	const data = new FuzzedDataProvider(fuzzerInputData);
	const intParam = data.consumeIntegral(4);
	const stringParam = data.consumeString(4, "utf-8");
	myAwesomeCode(intParam, stringParam);
};

For more information on how to use the FuzzedDataProvider class, please refer to the example, the tests, and the implementation of the FuzzedDataProvider class.

Fuzz target execution modes

Jazzer.js supports asynchronous fuzz targets out of the box, no special handling or configuration is needed.

Promise based execution

The resolution of a Promise, returned by a fuzz target, is awaited before the next fuzzing input is provided. This enables the fuzzing of async/await and Promise based code.

An example of a Promise based fuzz target can be found at tests/promise/fuzz.js.

Done callback based execution

If the fuzz target takes a callback function as second parameter, the fuzzer will await its invocation before providing the next input.

Invoking the callback function without a parameter indicates a successful execution, whereas invoking it with a parameter indicates a failure. In the error case, the passed in object is normally of type string or Error and used during reporting of the test execution.

An example of a done callback based fuzz target can be found at tests/done_callback/fuzz.js.

Synchronous execution

Asynchronous code needs careful synchronization between the Node.js Event Loop and the fuzzing thread, hence, provides a lower throughput compared to synchronous fuzzing. Despite that, asynchronous fuzzing is the default mode of Jazzer.js due to its prevalence in the JavaScript ecosystem and because it works for all fuzz targets.

Solely synchronous code can participate in the enhanced performance of synchronous fuzzing by setting the --sync flag when starting the fuzzer.

Using TypeScript to write fuzz targets

It is also possible to use TypeScript, or in that matter any other language transpiling to JavaScript, to write fuzz targets, as long as a module exporting a fuzz function is generated.

An example on how to use TypeScript to fuzz a library can be found at examples/js-yaml/package.json.

Note: Directly executing fuzz targets written in TypeScript is NOT supported! However, it is possible to use the Jest integration to execute Jest fuzz tests written in TypeScript.

⚠️ Using Jazzer.js on pure ESM projects ⚠️

ESM brings a couple of challenges to the table, which are currently not fully solved. Jazzer.js does have general ESM support as in your project should be loaded properly. If your project internally still relies on calls to require(), all of these dependencies will be hooked. However, pure ECMAScript projects will currently not be instrumented!

The Jest integration can improve on this and use Jest's ESM features to properly transform external code and dependencies. However, ESM support in Jest is also only experimental.

One such example that Jazzer.js can handle just fine can be found at examples/protobufjs/fuzz.js:

import proto from "protobufjs";
import { temporaryWriteSync } from "tempy";

describe("protobufjs", () => {
	test.fuzz("loadSync", (data) => {
		const file = temporaryWriteSync(data);
		proto.loadSync(file);
	});
});

You also have to adapt your package.json accordingly, by adding:

{
	"type": "module"
}

Running the fuzz target

After adding @jazzer.js/core as a dev-dependency to a project, the fuzzer can execute a fuzz target using the jazzer npm command. To do so, use npx:

npx jazzer <fuzzer parameters>

Or add a new script to your package.json:

"scripts": {
"fuzz": "jazzer <fuzzer parameters>"
}

Inputs triggering issues, like uncaught exceptions, timeouts, etc., are stored in the current working directory with an auto-generated name.

The general command format is:

jazzer <fuzzTarget> <fuzzerFlags> [corpus...] [-- <fuzzingEngineFlags>]

Detailed documentation and some example calls are available using the --help flag, so that only the most important parameters are discussed here.

Parameter Description
<fuzzTarget> Import path to the fuzz target module.
[corpus...] Paths to the corpus directories. If not given, no initial seeds are used nor interesting inputs saved.
-f, --fuzz_function Name of the fuzz test entry point. It must be an exported function with a single Buffer parameter. Default is fuzz.
-i, --includes / -e, --excludes Part of filepath names to include/exclude in the instrumentation. A tailing / should be used to include directories and prevent confusion with filenames. * can be used to include all files. Can be specified multiple times. Default will include everything outside the node_modules directory. If either of these flags are set the default value for the other is ignored.
--sync Enables synchronous fuzzing. May only be used for entirely synchronous code.
-h, --custom_hooks Filenames with custom hooks. Several hooks per file are possible. See further details in docs/fuzz-settings.md.
--help Detailed help message containing all flags.
-- <fuzzingEngineFlags> Parameters after -- are forwarded to the internal fuzzing engine (libFuzzer). Available settings can be found in its options documentation.

Coverage report generation

To generate a coverage report, add the --coverage flag to the Jazzer.js CLI. In the following example, the --coverage flag is combined with the mode flag -m=regression that only uses existing corpus entries without performing any fuzzing.

npx jazzer -m=regression <fuzzer parameters> --corpus <corpus directories> --cov -- <libFuzzer parameters>

Alternatively, you can add a new script to your package.json:

"scripts": {
 "coverage": "jazzer -m regression -i target -i another_target <fuzzer parameters> --corpus <corpus directories> --cov -- <libFuzzer parameters>"
}

Files matched by the flags --includes or --custom_hooks, and not matched by the flag --excludes will be included in the coverage report. It is recommended to disable coverage report generation during fuzzing, because of the substantial overhead that it adds.

Coverage report directory

By default, the coverage reports can be found in the ./coverage directory. This default directory can be changed by setting the flag --cov_dir=<another coverage directory>. A longer flag of the form --coverage_directory also exists.

Coverage reporters

The desired report format can be set by the flags --cov_reporters/--coverage_reporters, which by default is set to --coverage_reporters clover json lcov text. See here for a list of supported coverage reporters.