Skip to content
Draft

Afl #881

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
28d26b3
feat(fuzzer): add an internal LibAFL spike backend
oetr Apr 18, 2026
367794a
test(fuzzer): add Phase 0 LibAFL spike checks
oetr Apr 18, 2026
0a83999
feat(core): wire engine selection through core and addon APIs
oetr Apr 18, 2026
3bafae7
feat(fuzzer): harden the LibAFL runtime bridge and timeout path
oetr Apr 18, 2026
7649327
fix(core): validate LibAFL mode constraints explicitly
oetr Apr 18, 2026
9701989
fix(fuzzer): make LibAFL -runs track executions, not fuzz_one loops
oetr Apr 18, 2026
4683a48
docs(ci): ship dual-engine docs and Rust CI prerequisites
oetr Apr 18, 2026
53d199c
test(engine): cover LibAFL backend behavior in integration suites
oetr Apr 18, 2026
ef85c17
feat(fuzzer): productionize the LibAFL runtime
oetr Apr 19, 2026
6da049f
test(bench): add a 30-second engine smoke benchmark
oetr Apr 19, 2026
934a610
feat(fuzzer): add LibAFL compare-guided mutation
oetr Apr 19, 2026
34a703b
test: cap Jest worker usage
oetr Apr 19, 2026
a31ef86
feat(fuzzer): calibrate LibAFL queue entries
oetr Apr 19, 2026
d707641
feat(fuzzer): switch LibAFL to power queue scheduling
oetr Apr 19, 2026
e46a15b
feat(fuzzer): use LibAFL power mutational stage
oetr Apr 19, 2026
4a8ccaa
test(bench): add LibAFL anomaly smoke checks
oetr Apr 19, 2026
7d1f6b5
feat(fuzzer): add structured LibAFL status output
oetr Apr 19, 2026
3491c4e
test(bench): parse the new LibAFL done block
oetr Apr 19, 2026
fa8da91
feat(fuzzer): switch LibAFL start lines to [>]
oetr Apr 19, 2026
1ab6e08
feat(fuzzer): color LibAFL progress lines
oetr Apr 19, 2026
9ac61ae
fix(fuzzer): reduce LibAFL corpus-load chatter
oetr Apr 19, 2026
7d1a6fd
fix(fuzzer): polish LibAFL status edge cases
oetr Apr 19, 2026
666a815
fix(fuzzer): refine LibAFL status formatting
oetr Apr 19, 2026
a350ede
feat(fuzzer): print INITED blocks for LibAFL
oetr Apr 19, 2026
7ad443c
fix(fuzzer): align LibAFL INITED fields
oetr Apr 19, 2026
f6b1e0c
feat(fuzzer): emit LibAFL pulses after idle periods
oetr Apr 19, 2026
654663f
test(engine): cover LibAFL idle pulse timing
oetr Apr 19, 2026
a52710b
fix(libafl): support lazy ESM coverage
oetr Apr 20, 2026
c559d76
feat(afl): show corpus initialization as [i] instead of [+]
oetr Apr 21, 2026
d4aec3d
feat(afl): print timeouts properly
oetr Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
with:
node-version: 22
cache: "npm"
- name: rust
uses: dtolnay/rust-toolchain@stable
- name: MSVC (windows)
uses: ilammy/msvc-dev-cmd@v1
if: contains(matrix.os, 'windows')
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/run-all-tests-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
with:
node-version: 22
cache: "npm"
- name: rust
uses: dtolnay/rust-toolchain@stable
- name: install dependencies
run: npm ci
- name: build project
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/run-all-tests-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
with:
node-version: 22
cache: "npm"
- name: rust
uses: dtolnay/rust-toolchain@stable
- name: install dependencies
run: npm ci
- name: install clang-tidy
Expand Down Expand Up @@ -67,6 +69,8 @@ jobs:
with:
node-version: ${{ matrix.node }}
cache: "npm"
- name: rust
uses: dtolnay/rust-toolchain@stable
- name: MSVC (windows)
uses: ilammy/msvc-dev-cmd@v1
if: contains(matrix.os, 'windows')
Expand Down Expand Up @@ -95,6 +99,8 @@ jobs:
with:
node-version: 22
cache: "npm"
- name: rust
uses: dtolnay/rust-toolchain@stable
- name: MSVC (windows)
uses: ilammy/msvc-dev-cmd@v1
if: contains(matrix.os, 'windows')
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

Jazzer.js is a coverage-guided, in-process fuzzer for the
[Node.js](https://nodejs.org) platform developed by
[Code Intelligence](https://www.code-intelligence.com). It is based on
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and brings many of its
[Code Intelligence](https://www.code-intelligence.com). It supports
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and
[LibAFL](https://github.com/AFLplusplus/LibAFL) backends and brings
instrumentation-powered mutation features to the JavaScript ecosystem.

## Quickstart
Expand Down Expand Up @@ -47,6 +48,9 @@ To use Jazzer.js in your own project follow these few simple steps:
npx jazzer FuzzTarget
```

To run with the LibAFL backend instead of the default libFuzzer backend, add
`--engine=afl`.

4. Enjoy fuzzing!

## Usage
Expand Down
3 changes: 3 additions & 0 deletions benchmarks/engine_smoke/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
package-lock.json
work/
189 changes: 189 additions & 0 deletions benchmarks/engine_smoke/anomaly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright 2026 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const { spawnSync } = require("child_process");
const fs = require("fs");
const path = require("path");

const benchmarkDirectory = __dirname;
const workDirectory = path.join(benchmarkDirectory, "work", "anomalies");
const engineTarget = path.join(
benchmarkDirectory,
"..",
"..",
"tests",
"engine",
"fuzz.js",
);
const asyncTarget = path.join(benchmarkDirectory, "anomaly_fuzz.js");

function removeIfExists(targetPath) {
fs.rmSync(targetPath, { force: true, recursive: true });
}

function ensureDirectory(targetPath) {
fs.mkdirSync(targetPath, { recursive: true });
}

function runCommand(label, args, cwd, outputDirectory, expectedStatus = 0) {
console.log(`\n[anomaly] ${label}`);
console.log(`[anomaly] command: npx ${args.join(" ")}`);
ensureDirectory(outputDirectory);
const sanitizedLabel = label.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
const stdoutPath = path.join(outputDirectory, `${sanitizedLabel}.stdout.log`);
const stderrPath = path.join(outputDirectory, `${sanitizedLabel}.stderr.log`);
const stdoutFd = fs.openSync(stdoutPath, "w");
const stderrFd = fs.openSync(stderrPath, "w");
const startedAt = Date.now();
const proc = spawnSync("npx", args, {
cwd,
env: { ...process.env },
shell: true,
stdio: ["ignore", stdoutFd, stderrFd],
windowsHide: true,
});
const elapsedMs = Date.now() - startedAt;
fs.closeSync(stdoutFd);
fs.closeSync(stderrFd);

if (proc.status !== expectedStatus) {
throw new Error(
`${label} failed with exit code ${proc.status}\nSTDOUT (${stdoutPath}):\n${fs.readFileSync(stdoutPath, "utf8")}\nSTDERR (${stderrPath}):\n${fs.readFileSync(stderrPath, "utf8")}`,
);
}

return {
elapsedMs,
stderrPath,
stdoutPath,
};
}

function parseExecsPerSecond(stderrPath) {
const stderr = fs.readFileSync(stderrPath, "utf8");
const match = stderr.match(/speed:\s+([0-9.]+) exec\/s/);
if (!match) {
throw new Error(`No LibAFL done line found in ${stderrPath}`);
}
return Number.parseFloat(match[1]);
}

function runGuidedNumericSmoke() {
const outputDirectory = path.join(workDirectory, "guided-numeric");
const corpusDirectory = path.join(outputDirectory, "corpus");
removeIfExists(outputDirectory);
ensureDirectory(corpusDirectory);
fs.writeFileSync(path.join(corpusDirectory, "seed"), Buffer.alloc(4));

const result = runCommand(
"guided numeric solve",
[
"jazzer",
engineTarget,
"-f",
"guided_numeric",
"--engine=afl",
"--sync",
"--disable_bug_detectors=.*",
"--",
corpusDirectory,
"-runs=4000",
"-seed=1337",
"-max_len=16",
`-artifact_prefix=${outputDirectory}${path.sep}`,
],
benchmarkDirectory,
outputDirectory,
77,
);

const output =
fs.readFileSync(result.stdoutPath, "utf8") +
fs.readFileSync(result.stderrPath, "utf8");
if (!output.includes("AFL numeric guidance finding")) {
throw new Error("Guided numeric smoke did not report the expected finding");
}

return {
name: "guided-numeric",
elapsedMs: result.elapsedMs,
};
}

function runAsyncSmoke() {
const outputDirectory = path.join(workDirectory, "async-smoke");
const corpusDirectory = path.join(outputDirectory, "corpus");
removeIfExists(outputDirectory);
ensureDirectory(corpusDirectory);
fs.writeFileSync(path.join(corpusDirectory, "seed"), "async-seed");

const result = runCommand(
"async throughput smoke",
[
"jazzer",
asyncTarget,
"-f",
"async_smoke",
"--engine=afl",
"--disable_bug_detectors=.*",
"--",
corpusDirectory,
"-runs=2000",
"-seed=9001",
"-max_len=128",
`-artifact_prefix=${outputDirectory}${path.sep}`,
],
benchmarkDirectory,
outputDirectory,
);

const execsPerSecond = parseExecsPerSecond(result.stderrPath);
if (execsPerSecond <= 0) {
throw new Error("Async smoke reported a non-positive exec/sec rate");
}
if (result.elapsedMs > 30000) {
throw new Error(
`Async smoke took unexpectedly long: ${result.elapsedMs} ms`,
);
}

return {
name: "async-smoke",
elapsedMs: result.elapsedMs,
execsPerSecond,
};
}

function main() {
ensureDirectory(workDirectory);
const results = [runGuidedNumericSmoke(), runAsyncSmoke()];
for (const result of results) {
const stats = [`elapsed_ms=${result.elapsedMs}`];
if (result.execsPerSecond !== undefined) {
stats.push(`execs_per_second=${result.execsPerSecond}`);
}
console.log(`[anomaly] ${result.name}: ${stats.join(" ")}`);
}
fs.writeFileSync(
path.join(workDirectory, "results.json"),
JSON.stringify(results, null, 2),
);
console.log(
`\n[anomaly] wrote machine-readable results to ${path.join(workDirectory, "results.json")}`,
);
}

main();
32 changes: 32 additions & 0 deletions benchmarks/engine_smoke/anomaly_fuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2026 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

module.exports.async_smoke = function (data) {
let checksum = 0;
for (const byte of data) {
checksum = ((checksum * 33) ^ byte) & 0xffff;
}

return new Promise((resolve) => {
setImmediate(() => {
if (checksum === 0x1337) {
// Exercise an extra branch without turning this into a finding target.
checksum ^= data.length;
}
resolve(checksum);
});
});
};
76 changes: 76 additions & 0 deletions benchmarks/engine_smoke/fuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2026 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const qs = require("qs");

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

module.exports.fuzz = function (data) {
const provider = new FuzzedDataProvider(data);
const input = provider.consumeRemainingAsString();

const parseOptions = {
allowDots: provider.consumeBoolean(),
allowEmptyArrays: provider.consumeBoolean(),
allowPrototypes: provider.consumeBoolean(),
arrayLimit: provider.consumeIntegralInRange(0, 32),
charset: provider.pickValue(["utf-8", "iso-8859-1"]),
charsetSentinel: provider.consumeBoolean(),
comma: provider.consumeBoolean(),
decodeDotInKeys: provider.consumeBoolean(),
depth: provider.consumeIntegralInRange(0, 16),
duplicates: provider.pickValue(["combine", "first", "last"]),
ignoreQueryPrefix: provider.consumeBoolean(),
interpretNumericEntities: provider.consumeBoolean(),
parameterLimit: provider.consumeIntegralInRange(1, 256),
parseArrays: provider.consumeBoolean(),
plainObjects: provider.consumeBoolean(),
strictDepth: provider.consumeBoolean(),
strictNullHandling: provider.consumeBoolean(),
};

let parsed;
try {
parsed = qs.parse(input, parseOptions);
} catch {
return;
}

try {
qs.stringify(parsed, {
addQueryPrefix: provider.consumeBoolean(),
allowDots: provider.consumeBoolean(),
allowEmptyArrays: provider.consumeBoolean(),
arrayFormat: provider.pickValue([
"indices",
"brackets",
"repeat",
"comma",
]),
charset: provider.pickValue(["utf-8", "iso-8859-1"]),
charsetSentinel: provider.consumeBoolean(),
commaRoundTrip: provider.consumeBoolean(),
delimiter: provider.pickValue(["&", ";"]),
encode: provider.consumeBoolean(),
encodeDotInKeys: provider.consumeBoolean(),
indices: provider.consumeBoolean(),
skipNulls: provider.consumeBoolean(),
strictNullHandling: provider.consumeBoolean(),
});
} catch {
// Smoke target: ignore library-level parse/stringify failures.
}
};
15 changes: 15 additions & 0 deletions benchmarks/engine_smoke/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "jazzerjs-engine-smoke",
"version": "1.0.0",
"private": true,
"description": "Manual 30-second smoke benchmark for libFuzzer vs LibAFL.",
"scripts": {
"smoke": "node run.js",
"smoke:anomalies": "node anomaly.js"
},
"devDependencies": {
"@jazzer.js/core": "file:../../packages/core",
"istanbul-lib-coverage": "^3.2.2",
"qs": "^6.14.0"
}
}
Loading
Loading