Skip to content

Commit 5bd73d6

Browse files
committed
fix(fuzzer): contain sync stop callback errors
Reject stop callback failures on the JS promise instead of letting them unwind through the Rust execution callback. The new child-process regression reproduces the pre-fix Rust panic triggered by a SIGINT stop callback that throws.
1 parent 6aee6c2 commit 5bd73d6

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

packages/fuzzer/libafl_runtime.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,16 @@ void StoreDeferredRejection(AsyncFuzzTargetContext *context,
203203
context->deferred_rejection = Napi::Persistent(error);
204204
}
205205

206+
void RejectSyncLibAflRun(SyncFuzzTargetContext *context,
207+
const Napi::Value &error) {
208+
if (context->is_resolved) {
209+
return;
210+
}
211+
212+
context->is_resolved = true;
213+
context->deferred.Reject(error);
214+
}
215+
206216
void SettleLibAflRun(Napi::Env env, Napi::Promise::Deferred &deferred,
207217
bool &is_resolved, int status) {
208218
if (is_resolved) {
@@ -492,7 +502,31 @@ int ExecuteSyncInput(void *user_data, const uint8_t *data, size_t size) {
492502
exit_code = Napi::Number::New(context->env, context->signal_status);
493503
}
494504

495-
context->js_stop_callback.Call({exit_code});
505+
try {
506+
context->js_stop_callback.Call({exit_code});
507+
} catch (const Napi::Error &error) {
508+
context->signal_status = 0;
509+
RejectSyncLibAflRun(context, error.Value());
510+
return kExecutionFatal;
511+
} catch (const std::exception &exception) {
512+
context->signal_status = 0;
513+
RejectSyncLibAflRun(
514+
context, Napi::Error::New(context->env,
515+
std::string("Internal fuzzer error - ") +
516+
exception.what())
517+
.Value());
518+
return kExecutionFatal;
519+
} catch (...) {
520+
context->signal_status = 0;
521+
RejectSyncLibAflRun(
522+
context,
523+
Napi::Error::New(
524+
context->env,
525+
"Internal fuzzer error - stop callback threw a non-standard "
526+
"exception")
527+
.Value());
528+
return kExecutionFatal;
529+
}
496530
context->signal_status = 0;
497531
return kExecutionStop;
498532
}

packages/fuzzer/libafl_runtime.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,45 @@ describe("LibAFL runtime", () => {
258258
},
259259
);
260260

261+
it("rejects thrown stop callbacks instead of panicking through Rust", () => {
262+
const script = `
263+
const addon = require(${JSON.stringify(nativeAddonPath())});
264+
addon.registerCoverageMap(Buffer.alloc(${1 << 20}));
265+
addon.registerNewCounters(0, 512);
266+
267+
let invocations = 0;
268+
addon.startLibAfl(
269+
() => {
270+
if (invocations === 0) {
271+
process.kill(process.pid, "SIGINT");
272+
}
273+
invocations += 1;
274+
},
275+
${JSON.stringify({ ...libAflOptions, runs: 1 })},
276+
() => {
277+
throw new Error("stop callback boom");
278+
},
279+
)
280+
.then(() => process.exit(2))
281+
.catch((error) => {
282+
if (!String(error).includes("stop callback boom")) {
283+
console.error(error);
284+
process.exit(3);
285+
}
286+
process.exit(0);
287+
});
288+
289+
setTimeout(() => process.exit(4), 1000);
290+
`;
291+
292+
const result = spawnSync(process.execPath, ["-e", script], {
293+
encoding: "utf8",
294+
timeout: 5000,
295+
});
296+
297+
expect(result.status).toBe(0);
298+
});
299+
261300
it("records compare feedback in the shared native map", async () => {
262301
addon.clearCompareFeedbackMap();
263302

0 commit comments

Comments
 (0)