Skip to content

Commit 799edf3

Browse files
committed
refactor(options): align extracted options with LibAFL
Finish the options split by moving engine and mode checks into the leaf package and sharing the LibAFL backend DTO with the fuzzer package. This leaves one generic extraction commit at the start of the branch and one LibAFL-specific follow-up later in the stack.
1 parent aa10dd4 commit 799edf3

8 files changed

Lines changed: 56 additions & 85 deletions

File tree

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/options.test.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,10 @@ import fs from "fs";
1818
import os from "os";
1919
import path from "path";
2020

21-
import {
22-
defaultCLIOptions,
23-
defaultJestOptions,
24-
Options,
25-
OptionsManager,
26-
OptionSource,
27-
resolveEngine,
28-
} from "@jazzer.js/options";
21+
import { OptionsManager, OptionSource } from "@jazzer.js/options";
2922

3023
import { buildLibAflOptions, spawnsSubprocess } from "./options";
3124

32-
describe("options", () => {
33-
describe("merge", () => {
34-
it("keeps libFuzzer as default CLI engine", () => {
35-
expect(defaultCLIOptions.engine).toBe("libfuzzer");
36-
});
37-
38-
it("keeps libFuzzer as default Jest engine", () => {
39-
expect(defaultJestOptions.engine).toBe("libfuzzer");
40-
});
41-
});
42-
});
43-
4425
describe("buildLibFuzzerOptions", () => {
4526
describe("spawnsSubprocess", () => {
4627
it("checks if subprocess libFuzzer flags are present", () => {
@@ -57,22 +38,6 @@ describe("buildLibFuzzerOptions", () => {
5738
});
5839

5940
describe("libafl options", () => {
60-
it("normalizes engine aliases", () => {
61-
expect(resolveEngine("libfuzzer")).toBe("libfuzzer");
62-
expect(resolveEngine("afl")).toBe("libafl");
63-
expect(resolveEngine("libafl")).toBe("libafl");
64-
expect(() => resolveEngine("unknown")).toThrow("Unknown fuzzing engine");
65-
});
66-
67-
it("canonicalizes engine aliases during option merge", () => {
68-
const manager = new OptionsManager(OptionSource.DefaultJestOptions).merge(
69-
{ engine: "afl" },
70-
OptionSource.ConfigurationFile,
71-
);
72-
73-
expect(manager.get("engine")).toBe("libafl");
74-
});
75-
7641
it("builds structured LibAFL options from fuzzer options", () => {
7742
const manager = new OptionsManager(OptionSource.DefaultCLIOptions).merge(
7843
{

packages/core/options.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as util from "util";
2020
import * as tmp from "tmp";
2121

2222
import {
23+
type LibAflOptions,
2324
type OptionsManager,
2425
resolveEngine,
2526
toOptionsWithPrintableSources,
@@ -29,31 +30,22 @@ import { useDictionaryByParams } from "./dictionary";
2930

3031
export * from "@jazzer.js/options";
3132

32-
export type LibAflOptions = {
33-
mode: "fuzzing" | "regression";
34-
runs: number;
35-
seed: number;
36-
maxLen: number;
37-
timeoutMillis: number;
38-
maxTotalTimeSeconds: number;
39-
artifactPrefix: string;
40-
corpusDirectories: string[];
41-
dictionaryFiles: string[];
42-
};
43-
4433
export function buildLibFuzzerOptions(options: OptionsManager) {
4534
let params: string[] = [];
4635
params = optionDependentParams(options, params);
4736
params = forkedExecutionParams(params);
4837
params = useDictionaryByParams(params, options.get("dictionaryEntries"));
4938

39+
// libFuzzer has to ignore SIGINT and SIGTERM, as it interferes
40+
// with the Node.js signal handling.
5041
params = params.concat("-handle_int=0", "-handle_term=0", "-handle_segv=0");
5142

5243
printOptions(options);
5344
logInfoAboutFuzzerOptions(params);
5445
return params;
5546
}
5647

48+
// Backwards-compatible alias for existing call sites.
5749
export const buildFuzzerOption = buildLibFuzzerOptions;
5850

5951
export function buildLibAflOptions(options: OptionsManager): LibAflOptions {
@@ -114,6 +106,8 @@ export function buildLibAflOptions(options: OptionsManager): LibAflOptions {
114106
}
115107

116108
if (options.get("mode") === "regression") {
109+
// Regression mode should replay every available corpus input unless the
110+
// user asked to stop for some other reason, mirroring libFuzzer's behavior.
117111
runs = 0;
118112
}
119113

@@ -216,6 +210,7 @@ function optionDependentParams(
216210

217211
let opts = options.get("fuzzerOptions");
218212
if (options.get("mode") === "regression") {
213+
// The last provided option takes precedence
219214
opts = opts.concat("-runs=0");
220215
}
221216

packages/fuzzer/addon.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import * as fs from "fs";
1818
import * as path from "path";
1919

20+
import type { LibAflOptions } from "@jazzer.js/options";
21+
2022
export type FuzzTargetAsyncOrValue = (
2123
data: Buffer,
2224
) => unknown | Promise<unknown>;
@@ -26,18 +28,7 @@ export type FuzzTargetCallback = (
2628
) => unknown;
2729
export type FuzzTarget = FuzzTargetAsyncOrValue | FuzzTargetCallback;
2830
export type FuzzOpts = string[];
29-
30-
export type LibAflOptions = {
31-
mode: "fuzzing" | "regression";
32-
runs: number;
33-
seed: number;
34-
maxLen: number;
35-
timeoutMillis: number;
36-
maxTotalTimeSeconds: number;
37-
artifactPrefix: string;
38-
corpusDirectories: string[];
39-
dictionaryFiles: string[];
40-
};
31+
export type { LibAflOptions } from "@jazzer.js/options";
4132

4233
export type StartFuzzingSyncFn = (
4334
fuzzFn: FuzzTarget,

packages/fuzzer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
]
2929
},
3030
"dependencies": {
31+
"@jazzer.js/options": "4.0.0",
3132
"bindings": "^1.5.0",
3233
"cmake-js": "^8.0.0",
3334
"node-addon-api": "^8.7.0"

packages/fuzzer/tsconfig.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@
44
"rootDir": ".",
55
"outDir": "dist"
66
},
7-
"exclude": ["build", "dist", "runtime", "cmake-build-*"]
7+
"exclude": ["build", "dist", "runtime", "cmake-build-*"],
8+
"references": [
9+
{
10+
"path": "../options"
11+
}
12+
]
813
}

packages/options/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17+
export type LibAflOptions = {
18+
mode: "fuzzing" | "regression";
19+
runs: number;
20+
seed: number;
21+
maxLen: number;
22+
timeoutMillis: number;
23+
maxTotalTimeSeconds: number;
24+
artifactPrefix: string;
25+
corpusDirectories: string[];
26+
dictionaryFiles: string[];
27+
};
28+
1729
/**
1830
* Jazzer.js options structure expected by the fuzzer.
1931
*
@@ -137,6 +149,7 @@ export const fromSnakeCase: KeyFormatSource = (key: string): string => {
137149
group.toUpperCase().replace("_", ""),
138150
);
139151
};
152+
140153
export const fromSnakeCaseWithPrefix: (prefix: string) => KeyFormatSource = (
141154
prefix: string,
142155
): KeyFormatSource => {

packages/options/options.test.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -273,30 +273,6 @@ describe("options", () => {
273273
});
274274
});
275275

276-
describe("engine and mode", () => {
277-
it("normalizes engine aliases", () => {
278-
expect(resolveEngine("libfuzzer")).toBe("libfuzzer");
279-
expect(resolveEngine("afl")).toBe("libafl");
280-
expect(resolveEngine("libafl")).toBe("libafl");
281-
expect(() => resolveEngine("unknown")).toThrow("Unknown fuzzing engine");
282-
});
283-
284-
it("normalizes fuzzing modes", () => {
285-
expect(resolveMode("fuzzing")).toBe("fuzzing");
286-
expect(resolveMode("regression")).toBe("regression");
287-
expect(() => resolveMode("unknown")).toThrow("Unknown fuzzing mode");
288-
});
289-
290-
it("canonicalizes engine aliases during option merge", () => {
291-
const manager = new OptionsManager(OptionSource.DefaultJestOptions).merge(
292-
{ engine: "afl" },
293-
OptionSource.ConfigurationFile,
294-
);
295-
296-
expect(manager.get("engine")).toBe("libafl");
297-
});
298-
});
299-
300276
describe("KeyFormatSource", () => {
301277
describe("fromSnakeCase", () => {
302278
it("converts to camelCase", () => {
@@ -334,6 +310,30 @@ describe("KeyFormatSource", () => {
334310
});
335311
});
336312

313+
describe("engine and mode", () => {
314+
it("normalizes engine aliases", () => {
315+
expect(resolveEngine("libfuzzer")).toBe("libfuzzer");
316+
expect(resolveEngine("afl")).toBe("libafl");
317+
expect(resolveEngine("libafl")).toBe("libafl");
318+
expect(() => resolveEngine("unknown")).toThrow("Unknown fuzzing engine");
319+
});
320+
321+
it("normalizes fuzzing modes", () => {
322+
expect(resolveMode("fuzzing")).toBe("fuzzing");
323+
expect(resolveMode("regression")).toBe("regression");
324+
expect(() => resolveMode("unknown")).toThrow("Unknown fuzzing mode");
325+
});
326+
327+
it("canonicalizes engine aliases during option merge", () => {
328+
const manager = new OptionsManager(OptionSource.DefaultJestOptions).merge(
329+
{ engine: "afl" },
330+
OptionSource.ConfigurationFile,
331+
);
332+
333+
expect(manager.get("engine")).toBe("libafl");
334+
});
335+
});
336+
337337
function expectDefaultsExceptKeys(
338338
options: Options,
339339
source: OptionSource,

0 commit comments

Comments
 (0)