Skip to content

Commit cb88f56

Browse files
committed
test: ESM instrumentation integration test
A pure .mjs module compares input against a 16-byte random string literal. Without the ESM compare hooks replacing === with traceStrCmp, libFuzzer cannot discover the string by brute force. The test spawns jazzer, which instruments the ES module through the new loader hook, and asserts the fuzzer finds the secret within the run budget.
1 parent 21f7611 commit cb88f56

4 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const { FuzzTestBuilder, cleanCrashFilesIn } = require("../helpers.js");
18+
19+
// module.register() is needed for ESM loader hooks.
20+
const [major, minor] = process.versions.node.split(".").map(Number);
21+
const supportsEsmHooks = major > 20 || (major === 20 && minor >= 6);
22+
23+
const describeOrSkip = supportsEsmHooks ? describe : describe.skip;
24+
25+
describeOrSkip("ESM instrumentation", () => {
26+
afterAll(async () => {
27+
await cleanCrashFilesIn(__dirname);
28+
});
29+
30+
it("should find a 16-byte string via compare hooks in an ES module", () => {
31+
// target.mjs compares against the literal "a]3;d*F!pk29&bAc".
32+
// Without the ESM compare hooks replacing === with traceStrCmp,
33+
// libFuzzer cannot discover a 16-byte random string.
34+
const fuzzTest = new FuzzTestBuilder()
35+
.fuzzEntryPoint("fuzz")
36+
.fuzzFile("fuzz.mjs")
37+
.dir(__dirname)
38+
.sync(true)
39+
.disableBugDetectors([".*"])
40+
.expectedErrors("Error")
41+
.runs(5000000)
42+
.seed(111994470)
43+
.build();
44+
45+
fuzzTest.execute();
46+
expect(fuzzTest.stderr).toContain("Found the ESM secret!");
47+
});
48+
});

tests/esm_instrumentation/fuzz.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2026 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { checkSecret } from "./target.mjs";
18+
19+
/**
20+
* @param { Buffer } data
21+
*/
22+
export function fuzz(data) {
23+
checkSecret(data.toString());
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "jazzerjs-esm-instrumentation-test",
3+
"version": "1.0.0",
4+
"description": "Integration test: coverage-guided fuzzing of a pure ES module",
5+
"scripts": {
6+
"fuzz": "jest --config '{}'",
7+
"dryRun": "echo \"Skipped: requires Node >= 20.6 for ESM loader hooks\""
8+
},
9+
"devDependencies": {
10+
"@jazzer.js/core": "file:../../packages/core"
11+
}
12+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2026 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* A pure ES module with a string-literal comparison. The compare
19+
* hooks replace the === with a traceStrCmp call that leaks the
20+
* literal to libFuzzer's mutation engine. Without that feedback
21+
* a 16-byte random string cannot be found by brute force.
22+
*/
23+
export function checkSecret(s) {
24+
if (s === "a]3;d*F!pk29&bAc") {
25+
throw new Error("Found the ESM secret!");
26+
}
27+
}

0 commit comments

Comments
 (0)