diff --git a/src/bun.js/bindings/Fuzzilli.cpp b/src/bun.js/bindings/Fuzzilli.cpp new file mode 100644 index 0000000000..b343b02d69 --- /dev/null +++ b/src/bun.js/bindings/Fuzzilli.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(size_)) +#define READ_FROM_FUZZILLI(data_, size_) RELEASE_ASSERT(read(REPRL_CRFD, data_, size_) == static_cast(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 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(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(malloc(SHM_SIZE)); + + resetCoverageEdges(); + + sharedData->numEdges = static_cast(edgesStop - edgesStart); +} + +void Fuzzilli::readInput(Vector* 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 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(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 diff --git a/src/bun.js/bindings/Fuzzilli.h b/src/bun.js/bindings/Fuzzilli.h new file mode 100644 index 0000000000..f48e0cf398 --- /dev/null +++ b/src/bun.js/bindings/Fuzzilli.h @@ -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 +#include + +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* 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 diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index caf3c26aa2..73c747b8e3 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -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 diff --git a/src/cli.zig b/src/cli.zig index 245925dc34..9288d5905c 100644 --- a/src/cli.zig +++ b/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); }, diff --git a/src/cli/fuzzilli_command.zig b/src/cli/fuzzilli_command.zig new file mode 100644 index 0000000000..ef0c7ed109 --- /dev/null +++ b/src/cli/fuzzilli_command.zig @@ -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("error: 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("error: REPRL_CRFD (fd {d}) is not available. Run Bun under Fuzzilli.", .{REPRL_CRFD}); + Output.prettyErrorln("Example: fuzzilli --profile=bun /path/to/bun fuzzilli", .{}); + 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("error: 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("error: Could not create temp file", .{}); + Global.exit(1); + }; + defer temp_file.close(); + + _ = temp_file.writeAll(reprl_script) catch { + Output.prettyErrorln("error: Could not write temp file", .{}); + Global.exit(1); + }; + + Output.prettyErrorln("[FUZZILLI] Temp file written, booting JS runtime", .{}); + + // 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; + } +};