mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
test(fuzzing): Start adding fuzzilli
Co-authored-by: mschwarzl <mschwarzl@cloudflare.com>
This commit is contained in:
182
src/bun.js/bindings/Fuzzilli.cpp
Normal file
182
src/bun.js/bindings/Fuzzilli.cpp
Normal 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
|
||||
67
src/bun.js/bindings/Fuzzilli.h
Normal file
67
src/bun.js/bindings/Fuzzilli.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
12
src/cli.zig
12
src/cli.zig
@@ -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);
|
||||
},
|
||||
|
||||
66
src/cli/fuzzilli_command.zig
Normal file
66
src/cli/fuzzilli_command.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user