Skip to content

Commit 6186748

Browse files
committed
fix(fuzzer): order LibAFL async findings safely
Publish finding metadata before waking the native runtime so objective reporting and promise rejection stay consistent. Guard longjmp-based signal recovery so fatal signals cannot jump into stale execution frames.
1 parent 5c102ed commit 6186748

2 files changed

Lines changed: 218 additions & 49 deletions

File tree

packages/fuzzer/libafl_runtime.cpp

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,17 @@ struct SyncFuzzTargetContext {
144144
LibAflOptions options;
145145
SyncWatchdogState watchdog;
146146
volatile std::sig_atomic_t signal_status = 0;
147+
volatile std::sig_atomic_t execution_active = 0;
147148
volatile int sigints = 0;
148149
std::jmp_buf execution_context;
149150
};
150151

151152
struct AsyncExecutionState {
152153
std::promise<int> promise;
153154
std::atomic<bool> settled = false;
155+
bool done_called = false;
156+
bool done_succeeded = false;
157+
bool callback_invocation_completed = false;
154158
};
155159

156160
struct AsyncDataType {
@@ -173,8 +177,8 @@ struct AsyncFuzzTargetContext {
173177
LibAflOptions options;
174178
std::unique_ptr<ScopedLibAflRuntime> runtime_guard;
175179
bool is_resolved = false;
176-
bool is_done_called = false;
177180
int run_status = kRuntimeOk;
181+
volatile std::sig_atomic_t execution_active = 0;
178182
volatile int sigints = 0;
179183
std::jmp_buf execution_context;
180184
};
@@ -227,29 +231,77 @@ void SettleLibAflRun(Napi::Env env, Napi::Promise::Deferred &deferred,
227231
}
228232
}
229233

230-
bool TrySetExecutionStatus(const std::shared_ptr<AsyncExecutionState> &state,
231-
int status) {
234+
bool IsExecutionSettled(const std::shared_ptr<AsyncExecutionState> &state) {
235+
return state->settled.load(std::memory_order_acquire);
236+
}
237+
238+
bool TryClaimExecution(const std::shared_ptr<AsyncExecutionState> &state) {
232239
bool expected = false;
233240
if (!state->settled.compare_exchange_strong(expected, true,
234241
std::memory_order_acq_rel,
235242
std::memory_order_acquire)) {
236243
return false;
237244
}
245+
return true;
246+
}
247+
248+
void PublishExecutionStatus(const std::shared_ptr<AsyncExecutionState> &state,
249+
int status) {
238250
state->promise.set_value(status);
251+
}
252+
253+
bool TryPublishExecutionStatus(
254+
const std::shared_ptr<AsyncExecutionState> &state, int status) {
255+
if (!TryClaimExecution(state)) {
256+
return false;
257+
}
258+
PublishExecutionStatus(state, status);
239259
return true;
240260
}
241261

262+
Napi::Value NormalizeAsyncError(Napi::Env env, const Napi::Value &error) {
263+
if (error.IsObject()) {
264+
return error;
265+
}
266+
return Napi::Error::New(env, error.ToString()).Value();
267+
}
268+
242269
void ReportAsyncFinding(AsyncFuzzTargetContext *context, Napi::Env env,
243270
const std::shared_ptr<AsyncExecutionState> &state,
244271
const Napi::Value &error,
245272
const std::vector<uint8_t> &input) {
246-
StoreDeferredRejection(context, error);
247-
if (TrySetExecutionStatus(state, kExecutionFinding)) {
248-
const auto artifact =
249-
WriteArtifact(context->options.artifact_prefix, "crash", input.data(),
250-
input.size(), false);
251-
RecordFindingInfo(&gFindingInfo, artifact, DescribeJsError(env, error));
273+
if (!TryClaimExecution(state)) {
274+
return;
275+
}
276+
277+
auto normalized_error = NormalizeAsyncError(env, error);
278+
auto summary = std::string("The LibAFL backend found a crashing input");
279+
const auto artifact = WriteArtifact(context->options.artifact_prefix, "crash",
280+
input.data(), input.size(), false);
281+
try {
282+
summary = DescribeJsError(env, normalized_error);
283+
} catch (const std::exception &exception) {
284+
normalized_error =
285+
Napi::Error::New(env, std::string("Internal fuzzer error - ") +
286+
exception.what())
287+
.Value();
288+
summary = normalized_error.ToString().Utf8Value();
289+
}
290+
291+
RecordFindingInfo(&gFindingInfo, artifact, summary);
292+
StoreDeferredRejection(context, normalized_error);
293+
PublishExecutionStatus(state, kExecutionFinding);
294+
}
295+
296+
void ReportAsyncInternalError(AsyncFuzzTargetContext *context, Napi::Env env,
297+
const std::shared_ptr<AsyncExecutionState> &state,
298+
const std::string &message) {
299+
if (!TryClaimExecution(state)) {
300+
return;
252301
}
302+
303+
StoreDeferredRejection(context, Napi::Error::New(env, message).Value());
304+
PublishExecutionStatus(state, kExecutionFatal);
253305
}
254306

255307
void SettleAsyncLibAflRun(Napi::Env env, AsyncFuzzTargetContext *context) {
@@ -366,17 +418,26 @@ class ScopedSyncWatchdog {
366418
};
367419

368420
void SyncSigintHandler(int signum) {
369-
std::cerr << std::endl;
370-
gActiveSyncContext->signal_status = signum;
371-
if (gActiveSyncContext->sigints > 0) {
421+
auto *context = gActiveSyncContext;
422+
if (context == nullptr) {
423+
_Exit(libfuzzer::RETURN_CONTINUE);
424+
}
425+
426+
context->signal_status = signum;
427+
if (context->sigints > 0) {
372428
_Exit(libfuzzer::RETURN_CONTINUE);
373429
}
374-
gActiveSyncContext->sigints++;
430+
context->sigints++;
375431
}
376432

377433
void SyncErrorSignalHandler(int signum) {
378-
gActiveSyncContext->signal_status = signum;
379-
std::longjmp(gActiveSyncContext->execution_context, signum);
434+
auto *context = gActiveSyncContext;
435+
if (context == nullptr || context->execution_active == 0) {
436+
_Exit(libfuzzer::EXIT_ERROR_SEGV);
437+
}
438+
439+
context->signal_status = signum;
440+
std::longjmp(context->execution_context, signum);
380441
}
381442

382443
int ExecuteSyncInput(void *user_data, const uint8_t *data, size_t size) {
@@ -389,15 +450,19 @@ int ExecuteSyncInput(void *user_data, const uint8_t *data, size_t size) {
389450

390451
try {
391452
auto buffer = Napi::Buffer<uint8_t>::Copy(context->env, data, size);
392-
if (setjmp(context->execution_context) == 0) {
453+
context->execution_active = 1;
454+
const auto signal_status = setjmp(context->execution_context);
455+
if (signal_status == 0) {
393456
auto result = context->target.Call({buffer});
394457
if (result.IsPromise()) {
395458
AsyncReturnsHandler();
396459
} else {
397460
SyncReturnsHandler();
398461
}
399462
}
463+
context->execution_active = 0;
400464
} catch (const Napi::Error &error) {
465+
context->execution_active = 0;
401466
if (!context->is_resolved) {
402467
const auto artifact = WriteArtifact(context->options.artifact_prefix,
403468
"crash", data, size, false);
@@ -408,6 +473,7 @@ int ExecuteSyncInput(void *user_data, const uint8_t *data, size_t size) {
408473
}
409474
return kExecutionFinding;
410475
} catch (const std::exception &exception) {
476+
context->execution_active = 0;
411477
ExitWithUnexpectedError(exception);
412478
}
413479

@@ -439,19 +505,23 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function js_fuzz_callback,
439505

440506
try {
441507
if (context->sigints > 0) {
442-
TrySetExecutionStatus(state, kExecutionStop);
508+
TryPublishExecutionStatus(state, kExecutionStop);
443509
return;
444510
}
445511

446-
if (setjmp(context->execution_context) == SIGSEGV) {
512+
context->execution_active = 1;
513+
const auto signal_status = setjmp(context->execution_context);
514+
if (signal_status == SIGSEGV) {
515+
context->execution_active = 0;
447516
std::cerr << "==" << static_cast<unsigned long>(GetPID())
448517
<< "== Segmentation Fault" << std::endl;
449518
libfuzzer::PrintCrashingInput();
450519
_Exit(libfuzzer::EXIT_ERROR_SEGV);
451520
}
452521

453522
if (env == nullptr) {
454-
TrySetExecutionStatus(state, kExecutionFatal);
523+
context->execution_active = 0;
524+
TryPublishExecutionStatus(state, kExecutionFatal);
455525
return;
456526
}
457527

@@ -463,13 +533,12 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function js_fuzz_callback,
463533
.Int32Value();
464534

465535
if (parameter_count > 1) {
466-
context->is_done_called = false;
467536
auto done = Napi::Function::New(env, [=](const Napi::CallbackInfo &info) {
468-
if (context->is_resolved) {
537+
if (IsExecutionSettled(state)) {
469538
return;
470539
}
471540

472-
if (context->is_done_called) {
541+
if (state->done_called) {
473542
auto error =
474543
Napi::Error::New(env, "Expected done to be called once, but it "
475544
"was called multiple times.")
@@ -478,21 +547,24 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function js_fuzz_callback,
478547
return;
479548
}
480549

481-
context->is_done_called = true;
550+
state->done_called = true;
482551
const auto has_error =
483552
info.Length() > 0 && !(info[0].IsNull() || info[0].IsUndefined());
484553
if (has_error) {
485-
auto error = info[0];
486-
if (!error.IsObject()) {
487-
error = Napi::Error::New(env, error.ToString()).Value();
488-
}
489-
ReportAsyncFinding(context, env, state, error, current_input);
554+
ReportAsyncFinding(context, env, state, info[0], current_input);
555+
return;
556+
}
557+
558+
if (state->callback_invocation_completed) {
559+
TryPublishExecutionStatus(state, kExecutionContinue);
490560
} else {
491-
TrySetExecutionStatus(state, kExecutionContinue);
561+
state->done_succeeded = true;
492562
}
493563
});
494564

495565
auto result = js_fuzz_callback.Call({buffer, done});
566+
state->callback_invocation_completed = true;
567+
context->execution_active = 0;
496568
if (result.IsPromise()) {
497569
AsyncReturnsHandler();
498570
auto error =
@@ -502,19 +574,23 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function js_fuzz_callback,
502574
ReportAsyncFinding(context, env, state, error, current_input);
503575
} else {
504576
SyncReturnsHandler();
577+
if (state->done_succeeded) {
578+
TryPublishExecutionStatus(state, kExecutionContinue);
579+
}
505580
}
506581
return;
507582
}
508583

509584
auto result = js_fuzz_callback.Call({buffer});
585+
context->execution_active = 0;
510586
if (result.IsPromise()) {
511587
AsyncReturnsHandler();
512588
auto js_promise = result.As<Napi::Object>();
513589
auto then = js_promise.Get("then").As<Napi::Function>();
514590
then.Call(js_promise,
515591
{Napi::Function::New(env,
516592
[=](const Napi::CallbackInfo &) {
517-
TrySetExecutionStatus(
593+
TryPublishExecutionStatus(
518594
state, kExecutionContinue);
519595
}),
520596
Napi::Function::New(env, [=](const Napi::CallbackInfo &info) {
@@ -523,37 +599,43 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function js_fuzz_callback,
523599
? info[0]
524600
: Napi::Error::New(env, "Unknown promise rejection")
525601
.Value();
526-
if (!error.IsObject()) {
527-
error = Napi::Error::New(env, error.ToString()).Value();
528-
}
529602
ReportAsyncFinding(context, env, state, error,
530603
current_input);
531604
})});
532605
} else {
533606
SyncReturnsHandler();
534-
TrySetExecutionStatus(state, kExecutionContinue);
607+
TryPublishExecutionStatus(state, kExecutionContinue);
535608
}
536609
} catch (const Napi::Error &error) {
610+
context->execution_active = 0;
537611
ReportAsyncFinding(context, env, state, error.Value(), current_input);
538612
} catch (const std::exception &exception) {
539-
if (TrySetExecutionStatus(state, kExecutionFatal)) {
540-
auto message =
541-
std::string("Internal fuzzer error - ").append(exception.what());
542-
StoreDeferredRejection(context, Napi::Error::New(env, message).Value());
543-
}
613+
context->execution_active = 0;
614+
ReportAsyncInternalError(
615+
context, env, state,
616+
std::string("Internal fuzzer error - ").append(exception.what()));
544617
}
545618
}
546619

547620
void AsyncSigintHandler(int signum) {
548-
std::cerr << std::endl;
549-
if (gActiveAsyncContext->sigints > 0) {
621+
auto *context = gActiveAsyncContext;
622+
if (context == nullptr) {
550623
_Exit(libfuzzer::RETURN_CONTINUE);
551624
}
552-
gActiveAsyncContext->sigints = signum;
625+
626+
if (context->sigints > 0) {
627+
_Exit(libfuzzer::RETURN_CONTINUE);
628+
}
629+
context->sigints = signum;
553630
}
554631

555632
void AsyncErrorSignalHandler(int signum) {
556-
std::longjmp(gActiveAsyncContext->execution_context, signum);
633+
auto *context = gActiveAsyncContext;
634+
if (context == nullptr || context->execution_active == 0) {
635+
_Exit(libfuzzer::EXIT_ERROR_SEGV);
636+
}
637+
638+
std::longjmp(context->execution_context, signum);
557639
}
558640

559641
int ExecuteAsyncInput(void *user_data, const uint8_t *data, size_t size) {

0 commit comments

Comments
 (0)