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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ node_modules/

# Output of 'npm pack'
*.tgz

# Dictionaries generated by Jazzer.js
.JazzerJs-merged-dictionaries
29 changes: 21 additions & 8 deletions packages/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import * as reports from "istanbul-reports";

import * as fuzzer from "@jazzer.js/fuzzer";
import * as hooking from "@jazzer.js/hooking";
import { clearFirstFinding, getFirstFinding, printFinding } from "./finding";
import {
clearFirstFinding,
getFirstFinding,
printFinding,
Finding,
} from "./finding";
import {
FileSyncIdStrategy,
Instrumentor,
Expand All @@ -42,6 +47,8 @@ tmp.setGracefulCleanup();
const ERROR_EXPECTED_CODE = 0;
const ERROR_UNEXPECTED_CODE = 78;

const SIGSEGV = 11;

export interface Options {
// `fuzzTarget` is the name of an external module containing a `fuzzer.FuzzTarget`
// that is resolved by `fuzzEntryPoint`.
Expand Down Expand Up @@ -190,18 +197,18 @@ export async function startFuzzingNoInit(
fuzzFn: fuzzer.FuzzTarget,
options: Options,
) {
// Signal handler that stops fuzzing when the process receives a SIGINT,
// Signal handler that stops fuzzing when the process receives a SIGINT/SIGSEGV,
// necessary to generate coverage reports and print debug information.
// The handler stops the process via `stopFuzzing`, as resolving the "fuzzing
// promise" does not work in sync mode due to the blocked event loop.
const signalHandler = () => {
const signalHandler = (exitCode: number) => {
stopFuzzing(
undefined,
options.expectedErrors,
options.coverageDirectory,
options.coverageReporters,
options.sync,
0,
exitCode,
);
};

Expand All @@ -212,15 +219,16 @@ export async function startFuzzingNoInit(
Fuzzer.startFuzzing(
fuzzFn,
fuzzerOptions,
// In synchronous mode, we cannot use the SIGINT handler in Node,
// In synchronous mode, we cannot use the SIGINT/SIGSEGV handler in Node,
// because it won't be called until the fuzzing process is finished.
// Hence, we pass a callback function to the native fuzzer.
// The appropriate exitCode for the signalHandler will be added by the native fuzzer.
signalHandler,
),
);
} else {
// Add a Node SIGINT handler to stop fuzzing gracefully.
process.on("SIGINT", signalHandler);
process.on("SIGINT", () => signalHandler(0));
process.on("SIGSEGV", () => signalHandler(SIGSEGV));
return Fuzzer.startFuzzingAsync(fuzzFn, fuzzerOptions);
}
}
Expand Down Expand Up @@ -251,14 +259,19 @@ function stopFuzzing(
);
}

// Prioritize findings over segfaults.
if (forceShutdownWithCode === SIGSEGV && !(err instanceof Finding)) {
err = new Finding("Segmentation Fault");
}

// No error found, check if one is expected or an exit code should be enforced.
if (!err) {
if (expectedErrors.length) {
console.error(
`ERROR: Received no error, but expected one of [${expectedErrors}].`,
);
stopFuzzing(ERROR_UNEXPECTED_CODE);
} else if (forceShutdownWithCode !== undefined) {
} else if (forceShutdownWithCode === 0) {
stopFuzzing(forceShutdownWithCode);
}
return;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function buildFuzzerOption(options: Options) {

// libFuzzer has to ignore SIGINT and SIGTERM, as it interferes
// with the Node.js signal handling.
params = params.concat("-handle_int=0", "-handle_term=0");
params = params.concat("-handle_int=0", "-handle_term=0", "-handle_segv=0");

if (process.env.JAZZER_DEBUG) {
console.debug("DEBUG: [core] Jazzer.js actually used fuzzer arguments: ");
Expand Down
2 changes: 1 addition & 1 deletion packages/fuzzer/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type FuzzOpts = string[];
export type StartFuzzingSyncFn = (
fuzzFn: FuzzTarget,
fuzzOpts: FuzzOpts,
sigintCallback: () => void,
jsStopCallback: (signal: number) => void,
) => void;
export type StartFuzzingAsyncFn = (
fuzzFn: FuzzTarget,
Expand Down
16 changes: 12 additions & 4 deletions packages/fuzzer/fuzzing_sync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace {
struct FuzzTargetInfo {
Napi::Env env;
Napi::Function target;
Napi::Function stopFunction;
Napi::Function jsStopCallback; // JS stop function used by signal handling.
};

// The JS fuzz target. We need to store the function pointer in a global
Expand Down Expand Up @@ -70,9 +70,16 @@ int FuzzCallbackSync(const uint8_t *Data, size_t Size) {
SyncReturnsHandler();
}

// Execute the signal handler in context of the node application.
if (gSignalStatus != 0) {
gFuzzTarget->stopFunction.Call({});
// Non-zero exit codes will produce crash files.
auto exitCode = Napi::Number::New(gFuzzTarget->env, 0);

if (gSignalStatus != SIGINT) {
exitCode = Napi::Number::New(gFuzzTarget->env, gSignalStatus);
}

// Execute the signal handler in context of the node application.
gFuzzTarget->jsStopCallback.Call({exitCode});
}

return EXIT_SUCCESS;
Expand All @@ -99,11 +106,12 @@ void StartFuzzing(const Napi::CallbackInfo &info) {

// Store the JS fuzz target and corresponding environment globally, so that
// our C++ fuzz target can use them to call back into JS. Also store the stop
// function that will be called in case of a SIGINT.
// function that will be called in case of a SIGINT/SIGSEGV.
gFuzzTarget = {info.Env(), info[0].As<Napi::Function>(),
info[2].As<Napi::Function>()};

signal(SIGINT, sigintHandler);
signal(SIGSEGV, sigintHandler);

StartLibFuzzer(fuzzer_args, FuzzCallbackSync);
// Explicitly reset the global function pointer because the JS
Expand Down
9 changes: 7 additions & 2 deletions packages/fuzzer/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "utils.h"
#include "napi.h"
#include "shared/libfuzzer.h"
#include <csignal>
#include <iostream>

void StartLibFuzzer(const std::vector<std::string> &args,
Expand Down Expand Up @@ -75,7 +76,7 @@ void ReturnValueInfo(bool is_sync_runner) {
<< "\n== Jazzer.js:\n"
<< " Exclusively observed synchronous return values from fuzzed "
"function."
<< " Fuzzing in synchronous mode seems benefical!\n"
<< " Fuzzing in synchronous mode seems beneficial!\n"
<< " To enable it, append a `--sync` to your Jazzer.js invocation."
<< std::endl;
}
Expand All @@ -84,7 +85,7 @@ void ReturnValueInfo(bool is_sync_runner) {
std::cerr << "\n== Jazzer.js:\n"
<< " Observed asynchronous return values from "
"fuzzed function."
<< " Fuzzing in asynchronous mode seems benefical!\n"
<< " Fuzzing in asynchronous mode seems beneficial!\n"
<< " Remove the `--sync` flag from your Jazzer.js invocation."
<< std::endl;
}
Expand All @@ -96,6 +97,10 @@ int StopFuzzingHandleExit(const Napi::CallbackInfo &info) {

if (info[0].IsNumber()) {
exitCode = info[0].As<Napi::Number>().Int32Value();

if (exitCode == SIGSEGV) {
libfuzzer::PrintCrashingInput();
}
} else {
// If a dedicated status code is provided, the run is executed as internal
// test and the crashing input does not need to be printed/saved.
Expand Down
4 changes: 2 additions & 2 deletions tests/return_values/return_values.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
const { spawnSync } = require("child_process");
const path = require("path");
const SyncInfo =
"Exclusively observed synchronous return values from fuzzed function. Fuzzing in synchronous mode seems benefical!";
"Exclusively observed synchronous return values from fuzzed function. Fuzzing in synchronous mode seems beneficial!";
const AsyncInfo =
"Observed asynchronous return values from fuzzed function. Fuzzing in asynchronous mode seems benefical!";
"Observed asynchronous return values from fuzzed function. Fuzzing in asynchronous mode seems beneficial!";

// current working directory
const testDirectory = __dirname;
Expand Down
6 changes: 3 additions & 3 deletions tests/signal_handlers/SIGINT/fuzz.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ let i = 0;

module.exports.SIGINT_SYNC = (data) => {
if (i === 1000) {
console.log("kill with SIGINT");
console.log("kill with signal");
process.kill(process.pid, "SIGINT");
}
if (i > 1000) {
console.log("SIGINT has not stopped the fuzzing process");
console.log("Signal has not stopped the fuzzing process");
}
i++;
};
Expand All @@ -31,7 +31,7 @@ module.exports.SIGINT_ASYNC = (data) => {
// Raising SIGINT in async mode does not stop the fuzzer directly,
// as the event is handled asynchronously in the event loop.
if (i === 1000) {
console.log("kill with SIGINT");
console.log("kill with signal");
process.kill(process.pid, "SIGINT");
}
i++;
Expand Down
2 changes: 2 additions & 0 deletions tests/signal_handlers/SIGSEGV/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tests.fuzz
.jazzerjsrc.json
38 changes: 38 additions & 0 deletions tests/signal_handlers/SIGSEGV/fuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

let i = 0;

module.exports.SIGSEGV_SYNC = (data) => {
if (i === 1000) {
console.log("kill with signal");
process.kill(process.pid, "SIGSEGV");
}
if (i > 1000) {
console.log("Signal has not stopped the fuzzing process");
}
i++;
};

module.exports.SIGSEGV_ASYNC = (data) => {
// Raising SIGSEGV in async mode does not stop the fuzzer directly,
// as the event is handled asynchronously in the event loop.
if (i === 1000) {
console.log("kill with signal");
process.kill(process.pid, "SIGSEGV");
}
i++;
};
26 changes: 26 additions & 0 deletions tests/signal_handlers/SIGSEGV/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "jazzerjs-signal-handler-tests",
"version": "1.0.0",
"description": "Tests for the SIGINT signal handler",
"scripts": {
"test": "jest",
"fuzz": "JAZZER_FUZZ=1 jest"
},
"devDependencies": {
"@jazzer.js/jest-runner": "file:../../packages/jest-runner"
},
"jest": {
"projects": [
{
"runner": "@jazzer.js/jest-runner",
"displayName": {
"name": "Jazzer.js",
"color": "cyan"
},
"testMatch": [
"<rootDir>/**/*.fuzz.js"
]
}
]
}
}
22 changes: 22 additions & 0 deletions tests/signal_handlers/SIGSEGV/tests.fuzz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Code Intelligence GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const { SIGSEGV_ASYNC, SIGSEGV_SYNC } = require("./fuzz.js");

describe("Jest", () => {
it.fuzz("Sync", SIGSEGV_SYNC);
it.fuzz("Async", SIGSEGV_ASYNC);
});
Loading