Skip to content

Commit d85e319

Browse files
committed
Fix async bug detector wrapper
Await the execution of returned promises in the async bug detector wrapper, as otherwise findings would only be thrown in the next invocation.
1 parent 0290471 commit d85e319

File tree

3 files changed

+46
-15
lines changed

3 files changed

+46
-15
lines changed

packages/core/core.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ import * as reports from "istanbul-reports";
2525
import * as fuzzer from "@jazzer.js/fuzzer";
2626
import * as hooking from "@jazzer.js/hooking";
2727
import {
28-
loadBugDetectors,
28+
clearFirstFinding,
2929
Finding,
3030
getFirstFinding,
31-
clearFirstFinding,
31+
loadBugDetectors,
3232
} from "@jazzer.js/bug-detectors";
3333
import {
34-
registerInstrumentor,
35-
Instrumentor,
3634
FileSyncIdStrategy,
35+
Instrumentor,
3736
MemorySyncIdStrategy,
37+
registerInstrumentor,
3838
} from "@jazzer.js/instrumentor";
3939
import { builtinModules } from "module";
4040

@@ -404,40 +404,54 @@ export function wrapFuzzFunctionForBugDetection(
404404
let result: void | Promise<void>;
405405
try {
406406
result = (originalFuzzFn as fuzzer.FuzzTargetAsyncOrValue)(data);
407+
// Explicitly handle Promises and, if none, return the result
408+
// directly to still support sync fuzz targets.
409+
if (result instanceof Promise) {
410+
return result.then(
411+
(result) => {
412+
return throwIfError() ?? result;
413+
},
414+
(reason) => {
415+
return throwIfError(reason);
416+
}
417+
);
418+
}
407419
} catch (e) {
408420
fuzzTargetError = e;
409421
}
410-
return handleErrors(result, fuzzTargetError);
422+
return throwIfError(fuzzTargetError) ?? result;
411423
};
412424
} else {
413-
return (data: Buffer, done: (err?: Error) => void): void => {
414-
let fuzzTargetError: unknown;
425+
return (
426+
data: Buffer,
427+
done: (err?: Error) => void
428+
): void | Promise<void> => {
415429
try {
416-
originalFuzzFn(data, (err?: Error) => {
430+
// Return result of fuzz target to enable sanity checks in C++ part.
431+
return originalFuzzFn(data, (err?: Error) => {
417432
const finding = getFirstFinding();
418433
if (finding !== undefined) {
419434
clearFirstFinding();
420435
}
421436
done(finding ?? err);
422437
});
423438
} catch (e) {
424-
fuzzTargetError = e;
439+
throwIfError(e);
425440
}
426-
handleErrors(undefined, fuzzTargetError);
427441
};
428442
}
429443
}
430444

431-
function handleErrors(result: void | Promise<void>, fuzzTargetError: unknown) {
445+
function throwIfError(fuzzTargetError?: unknown) {
432446
const error = getFirstFinding();
433447
if (error !== undefined) {
434448
// The `firstFinding` is a global variable: we need to clear it after each fuzzing iteration.
435449
clearFirstFinding();
436450
throw error;
437-
} else if (fuzzTargetError !== undefined) {
451+
} else if (fuzzTargetError) {
438452
throw fuzzTargetError;
439453
}
440-
return result;
454+
return undefined;
441455
}
442456

443457
async function importModule(name: string): Promise<FuzzModule | void> {

tests/bug-detectors/bug-detectors.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe("General tests", () => {
3737

3838
it("Call with EVIL string; ASYNC", () => {
3939
const fuzzTest = new FuzzTestBuilder()
40-
.sync(false)
40+
.runs(0)
4141
.fuzzEntryPoint("CallOriginalEvilAsync")
4242
.dir(bugDetectorDirectory)
4343
.build();

tests/bug-detectors/general/fuzz.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,26 @@ const friendlyFile = "FRIENDLY";
2525
const friendlyCommand =
2626
(process.platform === "win32" ? "copy NUL " : "touch ") + friendlyFile;
2727

28+
let evilAsyncInvocations = 0;
2829
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2930
module.exports.CallOriginalEvilAsync = async function (data) {
30-
child_process.execSync(evilCommand);
31+
return new Promise((resolve) => {
32+
// Fuzz target is invoked two times. Skip the first one to verify that the
33+
// fuzzer reports the async finding of the second invocation.
34+
if (evilAsyncInvocations++ === 0) {
35+
resolve();
36+
return;
37+
}
38+
setTimeout(() => {
39+
try {
40+
child_process.execSync(evilCommand);
41+
} catch (ignored) {
42+
// Swallow exception to force out of band notification of finding.
43+
} finally {
44+
resolve();
45+
}
46+
}, 100);
47+
});
3148
};
3249

3350
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)