Skip to content

Commit

Permalink
Add done callback pattern support to fuzzer
Browse files Browse the repository at this point in the history
Allow a fuzz target to request a done callback as second parameter. If
used, the fuzz target has to invoke the done callback to report its
finished execution and request the next input.
  • Loading branch information
bertschneider committed Nov 28, 2022
1 parent 7062b9a commit 0f04f64
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 1 deletion.
4 changes: 3 additions & 1 deletion packages/fuzzer/fuzzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ function traceAndReturn(current: unknown, target: unknown, id: number) {
}

// Re-export everything from the native library.
export type FuzzFn = (data: Buffer) => void | Promise<void>;
export type FuzzFnAsyncOrValue = (data: Buffer) => void | Promise<void>;
export type FuzzFnCallback = (data: Buffer, done: (e: Error) => void) => void;
export type FuzzFn = FuzzFnAsyncOrValue | FuzzFnCallback;
export type FuzzOpts = string[];

export interface Fuzzer {
Expand Down
58 changes: 58 additions & 0 deletions packages/fuzzer/start_fuzzing_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct AsyncFuzzTargetContext {
std::thread native_thread;
Napi::Promise::Deferred deferred;
bool is_resolved = false;
bool is_done_called = false;
AsyncFuzzTargetContext() = delete;
};

Expand Down Expand Up @@ -104,6 +105,63 @@ void CallJsFuzzCallback(Napi::Env env, Napi::Function jsFuzzCallback,
try {
if (env != nullptr) {
auto buffer = Napi::Buffer<uint8_t>::Copy(env, data->data, data->size);

auto parameterCount = jsFuzzCallback.As<Napi::Object>()
.Get("length")
.As<Napi::Number>()
.Int32Value();
// In case more than one parameter is expected, the second one is
// considered to be a done callback to indicate finished execution.
if (parameterCount > 1) {
context->is_done_called = false;
auto done =
Napi::Function::New<>(env, [=](const Napi::CallbackInfo &info) {
// If the done callback based fuzz target also returned a promise
// is_resolved, could already been set and there's nothing to do
// anymore. As the done callback is executed on the main event
// loop, no synchronization for is_resolved is needed.
if (context->is_resolved) {
return;
}
// Mark if the done callback is invoked, to be able to check for
// wrongly returned promises.
context->is_done_called = true;

auto hasError = !(info[0].IsNull() || info[0].IsUndefined());
if (hasError) {
context->deferred.Reject(info[0].As<Napi::Error>().Value());
context->is_resolved = true;
data->promise->set_exception(
std::make_exception_ptr(JSException()));
} else {
data->promise->set_value(nullptr);
}
});
auto result = jsFuzzCallback.Call({buffer, done});
if (result.IsPromise()) {
// If the fuzz target received a done callback, but also returned a
// promise, the callback could already have been called. In that case
// is_done_callback is already set. If is_resolved is also set, the
// callback was invoked with an error and propagated that. If not, an
// appropriate error describing the illegal return value can be set.
// As everything is executed on the main event loop, no
// synchronization is needed.
if (context->is_resolved) {
return;
}
if (!context->is_done_called) {
data->promise->set_exception(
std::make_exception_ptr(JSException()));
}
context->deferred.Reject(
Napi::Error::New(env, "Internal fuzzer error - Either async or "
"done callback based fuzz tests allowed.")
.Value());
context->is_resolved = true;
}
return;
}

auto result = jsFuzzCallback.Call({buffer});

// Register callbacks on returned promise to await its resolution before
Expand Down

0 comments on commit 0f04f64

Please sign in to comment.