test(fuzzing): Start adding fuzzilli

Co-authored-by: mschwarzl <mschwarzl@cloudflare.com>
This commit is contained in:
Marko Vejnovic
2025-11-14 17:15:46 -08:00
parent 9513c1d1d9
commit bb1b93c6fb
5 changed files with 330 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#ifdef BUN_FUZZILLI_ENABLED
#include "config.h"
#include "Fuzzilli.h"
#include <fcntl.h>
#include <mutex>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wtf/Assertions.h>
#include <wtf/Compiler.h>
#include <wtf/DataLog.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/ASCIILiteral.h>
#define REPRL_CRFD 100
#define REPRL_CWFD 101
#define REPRL_DRFD 102
#define REPRL_DWFD 103
#define REPRL_MAX_DATA_SIZE (16 * 1024 * 1024)
#define SHM_SIZE 0x100000
#define MAX_EDGES ((SHM_SIZE - 4) * 8)
#define WRITE_TO_FUZZILLI(data_, size_) RELEASE_ASSERT(write(REPRL_CWFD, data_, size_) == static_cast<ssize_t>(size_))
#define READ_FROM_FUZZILLI(data_, size_) RELEASE_ASSERT(read(REPRL_CRFD, data_, size_) == static_cast<ssize_t>(size_))
struct Fuzzilli::SharedData* Fuzzilli::sharedData { nullptr };
uint32_t* Fuzzilli::edgesStart { nullptr };
uint32_t* Fuzzilli::edgesStop { nullptr };
char* Fuzzilli::reprlInputData { nullptr };
size_t Fuzzilli::numPendingRejectedPromises { 0 };
void Fuzzilli::resetCoverageEdges()
{
uint64_t n = 0;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
for (uint32_t* edge = edgesStart; edge < edgesStop && n < MAX_EDGES; edge++)
*edge = ++n;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
}
FilePrintStream& Fuzzilli::logFile()
{
static LazyNeverDestroyed<FilePrintStream> result;
static std::once_flag flag;
std::call_once(flag, []() {
if (FILE* file = fdopen(REPRL_DWFD, "w"))
result.construct(file, FilePrintStream::AdoptionMode::Adopt);
else {
result.construct(stdout, FilePrintStream::AdoptionMode::Borrow);
dataLogLn("Fuzzer output channel not available, printing to stdout instead.");
}
});
return result.get();
}
void Fuzzilli::waitForCommand()
{
unsigned action;
READ_FROM_FUZZILLI(&action, sizeof(action));
RELEASE_ASSERT_WITH_MESSAGE(action == 'cexe', "[REPRL] Unknown action: %u", action);
}
SUPPRESS_COVERAGE
void Fuzzilli::initializeCoverage(uint32_t* start, uint32_t* stop)
{
RELEASE_ASSERT_WITH_MESSAGE(!edgesStart && !edgesStop, "Coverage instrumentation is only supported for a single module");
edgesStart = start;
edgesStop = stop;
if (const char* shmKey = getenv("SHM_ID")) {
int32_t fd = shm_open(shmKey, O_RDWR, S_IREAD | S_IWRITE);
RELEASE_ASSERT_WITH_MESSAGE(fd >= 0, "Failed to open shared memory region: %s", strerror(errno));
sharedData = static_cast<SharedData*>(mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
RELEASE_ASSERT_WITH_MESSAGE(sharedData != MAP_FAILED, "Failed to mmap shared memory region");
dataLogLn("[COV] edge counters initialized. Shared memory: %s with %zu edges.", shmKey, edgesStop - edgesStart);
} else
sharedData = static_cast<SharedData*>(malloc(SHM_SIZE));
resetCoverageEdges();
sharedData->numEdges = static_cast<uint32_t>(edgesStop - edgesStart);
}
void Fuzzilli::readInput(Vector<char>* buffer)
{
size_t inputSize;
READ_FROM_FUZZILLI(&inputSize, sizeof(inputSize));
RELEASE_ASSERT(inputSize < REPRL_MAX_DATA_SIZE);
buffer->resize(inputSize);
memcpySpan(buffer->mutableSpan(), unsafeMakeSpan(reprlInputData, inputSize));
}
void Fuzzilli::flushReprl(int32_t result)
{
// In REPRL mode, stdout and stderr may be regular files, so we need to fflush them here.
fflush(stdout);
fflush(stderr);
// Check if any rejected promises weren't handled.
if (numPendingRejectedPromises > 0) {
numPendingRejectedPromises = 0;
result = 1;
}
int32_t status = (result & 0xff) << 8;
WRITE_TO_FUZZILLI(&status, sizeof(status));
resetCoverageEdges();
}
void Fuzzilli::initializeReprl()
{
std::array<char, 4> helo { 'H', 'E', 'L', 'O' };
WRITE_TO_FUZZILLI(helo.data(), helo.size());
READ_FROM_FUZZILLI(helo.data(), helo.size());
RELEASE_ASSERT_WITH_MESSAGE(equalSpans(std::span { helo } , "HELO"_span), "[REPRL] Invalid response from parent");
// Mmap the data input buffer.
reprlInputData = static_cast<char*>(mmap(0, REPRL_MAX_DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, REPRL_DRFD, 0));
RELEASE_ASSERT(reprlInputData != MAP_FAILED);
}
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop);
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop)
{
// Avoid duplicate initialization.
if (start == stop || *start)
return;
Fuzzilli::initializeCoverage(start, stop);
}
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard)
{
// There's a small race condition here: if this function executes in two threads for the same
// edge at the same time, the first thread might disable the edge (by setting the guard to zero)
// before the second thread fetches the guard value (and thus the index). However, our
// instrumentation ignores the first edge (see libcoverage.c) and so the race is unproblematic.
uint32_t index = *guard;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
Fuzzilli::sharedData->edges[index / 8] |= 1 << (index % 8);
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
*guard = 0;
}
#endif // BUN_FUZZILLI_ENABLED

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#pragma once
#ifdef BUN_FUZZILLI_ENABLED
#include <wtf/FilePrintStream.h>
#include <wtf/Vector.h>
namespace Fuzzilli {
struct SharedData {
uint32_t numEdges;
uint8_t edges[];
};
extern struct SharedData* sharedData;
extern uint32_t* edgesStart;
extern uint32_t* edgesStop;
extern char* reprlInputData;
extern size_t numPendingRejectedPromises;
void resetCoverageEdges();
FilePrintStream& logFile();
void waitForCommand();
void initializeCoverage(uint32_t* start, uint32_t* stop);
void readInput(Vector<char>* buffer);
void flushReprl(int32_t result);
void initializeReprl();
} // namespace Fuzzilli
#endif // BUN_FUZZILLI_ENABLED
namespace Fuzzilli {
constexpr void tryInitialize() noexcept {
#ifdef BUN_FUZZILLI_ENABLED
initializeReprl();
#endif
}
} // namespace Fuzzilli

View File

@@ -72,6 +72,7 @@
#include "ConsoleObject.h"
#include "DOMWrapperWorld-class.h"
#include "ErrorStackTrace.h"
#include "Fuzzilli.h"
#include "IDLTypes.h"
#include "ImportMetaObject.h"
#include "JS2Native.h"
@@ -324,6 +325,8 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
}
JSC::Options::assertOptionsAreCoherent();
}
Fuzzilli::tryInitialize();
}); // end std::call_once lambda
// NOLINTEND

View File

@@ -91,6 +91,7 @@ pub const PackCommand = @import("./cli/pack_command.zig").PackCommand;
pub const AuditCommand = @import("./cli/audit_command.zig").AuditCommand;
pub const InitCommand = @import("./cli/init_command.zig").InitCommand;
pub const WhyCommand = @import("./cli/why_command.zig").WhyCommand;
pub const FuzzilliCommand = @import("./cli/fuzzilli_command.zig").FuzzilliCommand;
pub const Arguments = @import("./cli/Arguments.zig");
@@ -624,6 +625,7 @@ pub const Command = struct {
RootCommandMatcher.case("prune") => .ReservedCommand,
RootCommandMatcher.case("list") => .PackageManagerCommand,
RootCommandMatcher.case("why") => .WhyCommand,
RootCommandMatcher.case("fuzzilli") => .FuzzilliCommand,
RootCommandMatcher.case("-e") => .AutoCommand,
@@ -933,6 +935,11 @@ pub const Command = struct {
try ExecCommand.exec(ctx);
} else Tag.printHelp(.ExecCommand, true);
},
.FuzzilliCommand => {
const ctx = try Command.init(allocator, log, .FuzzilliCommand);
try FuzzilliCommand.exec(ctx);
return;
},
}
}
@@ -968,6 +975,7 @@ pub const Command = struct {
PublishCommand,
AuditCommand,
WhyCommand,
FuzzilliCommand,
/// Used by crash reports.
///
@@ -1005,6 +1013,7 @@ pub const Command = struct {
.PublishCommand => 'k',
.AuditCommand => 'A',
.WhyCommand => 'W',
.FuzzilliCommand => 'F',
};
}
@@ -1317,6 +1326,9 @@ pub const Command = struct {
Output.pretty(intro_text, .{});
Output.flush();
},
.FuzzilliCommand => {
HelpCommand.printWithReason(.explicit, false);
},
else => {
HelpCommand.printWithReason(.explicit);
},

View File

@@ -0,0 +1,66 @@
const std = @import("std");
const bun = @import("bun");
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const std_posix = std.posix;
const Command = bun.cli.Command;
extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
const Run = bun.bun_js.Run;
pub const FuzzilliCommand = struct {
pub fn exec(ctx: Command.Context) !void {
@branchHint(.cold);
if (!Environment.isPosix) {
Output.prettyErrorln("<r><red>error<r>: Fuzzilli mode is only supported on POSIX systems", .{});
Global.exit(1);
}
// Set an environment variable so we can detect fuzzilli mode in JavaScript
_ = setenv("BUN_FUZZILLI_MODE", "1", 1);
// Verify REPRL file descriptors are available
const REPRL_CRFD: c_int = 100;
verifyFd(REPRL_CRFD) catch {
Output.prettyErrorln("<r><red>error<r>: REPRL_CRFD (fd {d}) is not available. Run Bun under Fuzzilli.", .{REPRL_CRFD});
Output.prettyErrorln("<r><d>Example: fuzzilli --profile=bun /path/to/bun fuzzilli<r>", .{});
Global.exit(1);
};
// Always embed the REPRL script (it's small and not worth the runtime overhead)
const reprl_script = @embedFile("../js/internal/fuzzilli-reprl-minimal.ts");
// Create temp file for the script
var temp_dir = std.fs.cwd().openDir("/tmp", .{}) catch {
Output.prettyErrorln("<r><red>error<r>: Could not access /tmp directory", .{});
Global.exit(1);
};
defer temp_dir.close();
const temp_file_name = "bun-fuzzilli-reprl.js";
const temp_file = temp_dir.createFile(temp_file_name, .{ .truncate = true }) catch {
Output.prettyErrorln("<r><red>error<r>: Could not create temp file", .{});
Global.exit(1);
};
defer temp_file.close();
_ = temp_file.writeAll(reprl_script) catch {
Output.prettyErrorln("<r><red>error<r>: Could not write temp file", .{});
Global.exit(1);
};
Output.prettyErrorln("<r><d>[FUZZILLI] Temp file written, booting JS runtime<r>", .{});
// Run the temp file
const temp_path = "/tmp/bun-fuzzilli-reprl.js";
try Run.boot(ctx, temp_path, null);
}
fn verifyFd(fd: c_int) !void {
const stat = try std_posix.fstat(fd);
_ = stat;
}
};