Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 28 additions & 13 deletions packages/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ import * as reports from "istanbul-reports";
import * as fuzzer from "@jazzer.js/fuzzer";
import * as hooking from "@jazzer.js/hooking";
import {
loadBugDetectors,
clearFirstFinding,
Finding,
getFirstFinding,
clearFirstFinding,
loadBugDetectors,
} from "@jazzer.js/bug-detectors";
import {
registerInstrumentor,
Instrumentor,
FileSyncIdStrategy,
Instrumentor,
MemorySyncIdStrategy,
registerInstrumentor,
} from "@jazzer.js/instrumentor";
import { builtinModules } from "module";

Expand Down Expand Up @@ -404,40 +404,55 @@ export function wrapFuzzFunctionForBugDetection(
let result: void | Promise<void>;
try {
result = (originalFuzzFn as fuzzer.FuzzTargetAsyncOrValue)(data);
// Explicitly set promise handlers to process findings, but still return
// the fuzz target result directly, so that sync execution is still
// possible.
if (result instanceof Promise) {
result = result.then(
(result) => {
return throwIfError() ?? result;
},
(reason) => {
return throwIfError(reason);
}
);
}
} catch (e) {
fuzzTargetError = e;
}
return handleErrors(result, fuzzTargetError);
return throwIfError(fuzzTargetError) ?? result;
};
} else {
return (data: Buffer, done: (err?: Error) => void): void => {
let fuzzTargetError: unknown;
return (
data: Buffer,
done: (err?: Error) => void
): void | Promise<void> => {
try {
originalFuzzFn(data, (err?: Error) => {
// Return result of fuzz target to enable sanity checks in C++ part.
return originalFuzzFn(data, (err?: Error) => {
const finding = getFirstFinding();
if (finding !== undefined) {
clearFirstFinding();
}
done(finding ?? err);
});
} catch (e) {
fuzzTargetError = e;
throwIfError(e);
}
handleErrors(undefined, fuzzTargetError);
};
}
}

function handleErrors(result: void | Promise<void>, fuzzTargetError: unknown) {
function throwIfError(fuzzTargetError?: unknown) {
const error = getFirstFinding();
if (error !== undefined) {
// The `firstFinding` is a global variable: we need to clear it after each fuzzing iteration.
clearFirstFinding();
throw error;
} else if (fuzzTargetError !== undefined) {
} else if (fuzzTargetError) {
throw fuzzTargetError;
}
return result;
return undefined;
}

async function importModule(name: string): Promise<FuzzModule | void> {
Expand Down
2 changes: 1 addition & 1 deletion tests/bug-detectors/bug-detectors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe("General tests", () => {

it("Call with EVIL string; ASYNC", () => {
const fuzzTest = new FuzzTestBuilder()
.sync(false)
.runs(0)
.fuzzEntryPoint("CallOriginalEvilAsync")
.dir(bugDetectorDirectory)
.build();
Expand Down
19 changes: 18 additions & 1 deletion tests/bug-detectors/general/fuzz.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,26 @@ const friendlyFile = "FRIENDLY";
const friendlyCommand =
(process.platform === "win32" ? "copy NUL " : "touch ") + friendlyFile;

let evilAsyncInvocations = 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
module.exports.CallOriginalEvilAsync = async function (data) {
child_process.execSync(evilCommand);
return new Promise((resolve) => {
// Fuzz target is invoked two times. Skip the first one to verify that the
// fuzzer reports the async finding of the second invocation.
if (evilAsyncInvocations++ === 0) {
resolve();
return;
}
setTimeout(() => {
try {
child_process.execSync(evilCommand);
} catch (ignored) {
// Swallow exception to force out of band notification of finding.
} finally {
resolve();
}
}, 100);
});
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down