diff --git a/cmake/sources/CxxSources.txt b/cmake/sources/CxxSources.txt index 04e1d2c79d..1999bcfdb3 100644 --- a/cmake/sources/CxxSources.txt +++ b/cmake/sources/CxxSources.txt @@ -10,6 +10,7 @@ src/bun.js/bindings/Base64Helpers.cpp src/bun.js/bindings/bindings.cpp src/bun.js/bindings/blob.cpp src/bun.js/bindings/bun-simdutf.cpp +src/bun.js/bindings/bun-spawn-darwin.cpp src/bun.js/bindings/bun-spawn.cpp src/bun.js/bindings/BunClientData.cpp src/bun.js/bindings/BunCommonStrings.cpp diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index 2261399b49..e3eb63fc59 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -121,6 +121,8 @@ src/bun.js/bindings/Exception.zig src/bun.js/bindings/FetchHeaders.zig src/bun.js/bindings/FFI.zig src/bun.js/bindings/generated_classes_list.zig +src/bun.js/bindings/GeneratedBindings.zig +src/bun.js/bindings/GeneratedJS2Native.zig src/bun.js/bindings/GetterSetter.zig src/bun.js/bindings/HTTPServerAgent.zig src/bun.js/bindings/JSArray.zig diff --git a/src/bun.js/api/bun/process.zig b/src/bun.js/api/bun/process.zig index 70bd011cdb..3917269e21 100644 --- a/src/bun.js/api/bun/process.zig +++ b/src/bun.js/api/bun/process.zig @@ -1237,8 +1237,11 @@ pub fn spawnProcessPosix( if (comptime Environment.isLinux) { attr.uid = options.uid; attr.gid = options.gid; + } else if (comptime Environment.isMac) { + // On macOS, uid/gid are handled separately in the custom spawn implementation + // We don't set them on the attr here } else { - // On non-Linux platforms, throw an error if uid/gid are specified + // On other platforms, throw an error if uid/gid are specified if (options.uid != null or options.gid != null) { return .{ .err = bun.sys.Error.fromCode(.PERM, .posix_spawn) }; } @@ -1472,6 +1475,66 @@ pub fn spawnProcessPosix( } const argv0 = options.argv0 orelse argv[0].?; + + // On macOS, if uid/gid are specified, we need to use a custom spawn implementation + // because posix_spawn doesn't support uid/gid changes + if (comptime Environment.isMac) { + if (options.uid != null or options.gid != null) { + // We need to use our custom fork+exec implementation + var chdir_buf: ?[:0]u8 = null; + defer if (chdir_buf) |buf| bun.default_allocator.free(buf); + + if (options.cwd.len > 0) { + chdir_buf = try bun.default_allocator.dupeZ(u8, options.cwd); + } + + const spawn_request = PosixSpawn.BunSpawnRequest{ + .chdir_buf = if (chdir_buf) |buf| buf.ptr else null, + .detached = options.detached, + .actions = .{ + .ptr = null, + .len = 0, + }, + .uid = options.uid orelse 0, + .gid = options.gid orelse 0, + .has_uid = options.uid != null, + .has_gid = options.gid != null, + }; + + const spawn_result = PosixSpawn.BunSpawnRequest.spawn( + argv0, + spawn_request, + argv, + envp, + ); + + // Continue with the rest of the function + var failed_after_spawn = false; + defer { + if (failed_after_spawn) { + for (to_close_on_error.items) |fd| { + fd.close(); + } + to_close_on_error.clearAndFree(); + } + } + + switch (spawn_result) { + .err => { + failed_after_spawn = true; + return .{ .err = spawn_result.err }; + }, + .result => |pid| { + spawned.pid = pid; + spawned.extra_pipes = extra_fds; + extra_fds = std.ArrayList(bun.FileDescriptor).init(bun.default_allocator); + + return .{ .result = spawned }; + }, + } + } + } + const spawn_result = PosixSpawn.spawnZ( argv0, actions, diff --git a/src/bun.js/api/bun/spawn.zig b/src/bun.js/api/bun/spawn.zig index d8cd2af049..a42a896c17 100644 --- a/src/bun.js/api/bun/spawn.zig +++ b/src/bun.js/api/bun/spawn.zig @@ -280,7 +280,7 @@ pub const PosixSpawn = struct { pub const Actions = if (Environment.isLinux) BunSpawn.Actions else PosixSpawnActions; pub const Attr = if (Environment.isLinux) BunSpawn.Attr else PosixSpawnAttr; - const BunSpawnRequest = extern struct { + pub const BunSpawnRequest = extern struct { chdir_buf: ?[*:0]u8 = null, detached: bool = false, actions: ActionsList = .{}, @@ -295,9 +295,8 @@ pub const PosixSpawn = struct { }; extern fn posix_spawn_bun( - pid: *c_int, - path: [*:0]const u8, request: *const BunSpawnRequest, + path: [*:0]const u8, argv: [*:null]?[*:0]const u8, envp: [*:null]?[*:0]const u8, ) isize; @@ -309,23 +308,21 @@ pub const PosixSpawn = struct { envp: [*:null]?[*:0]const u8, ) Maybe(pid_t) { var req = req_; - var pid: c_int = 0; - const rc = posix_spawn_bun(&pid, path, &req, argv, envp); + const rc = posix_spawn_bun(&req, path, argv, envp); if (comptime bun.Environment.allow_assert) - bun.sys.syslog("posix_spawn_bun({s}) = {d} ({d})", .{ + bun.sys.syslog("posix_spawn_bun({s}) = {d}", .{ bun.span(argv[0] orelse ""), rc, - pid, }); - if (rc == 0) { - return Maybe(pid_t){ .result = @intCast(pid) }; + if (rc > 0) { + return Maybe(pid_t){ .result = @intCast(rc) }; } return Maybe(pid_t){ .err = .{ - .errno = @as(bun.sys.Error.Int, @truncate(@intFromEnum(@as(std.c.E, @enumFromInt(rc))))), + .errno = @as(bun.sys.Error.Int, @truncate(@intFromEnum(@as(std.c.E, @enumFromInt(-rc))))), .syscall = .posix_spawn, .path = bun.span(argv[0] orelse ""), }, @@ -362,6 +359,7 @@ pub const PosixSpawn = struct { envp, ); } + var pid: pid_t = undefined; const rc = system.posix_spawn( diff --git a/src/bun.js/bindings/bun-spawn-darwin.cpp b/src/bun.js/bindings/bun-spawn-darwin.cpp new file mode 100644 index 0000000000..fe52d35cdf --- /dev/null +++ b/src/bun.js/bindings/bun-spawn-darwin.cpp @@ -0,0 +1,189 @@ +#include "root.h" + +#if OS(DARWIN) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern char** environ; + +enum FileActionType : uint8_t { + None, + Close, + Dup2, + Open, +}; + +typedef struct bun_spawn_request_file_action_t { + FileActionType type; + const char* path; + int fds[2]; + int flags; + int mode; +} bun_spawn_request_file_action_t; + +typedef struct bun_spawn_file_action_list_t { + const bun_spawn_request_file_action_t* ptr; + size_t len; +} bun_spawn_file_action_list_t; + +typedef struct bun_spawn_request_t { + const char* chdir; + bool detached; + bun_spawn_file_action_list_t actions; + uint32_t uid; + uint32_t gid; + bool has_uid; + bool has_gid; +} bun_spawn_request_t; + +extern "C" ssize_t posix_spawn_bun( + const bun_spawn_request_t* request, + const char* path, + char* const argv[], + char* const envp[]) +{ + // Check permissions before forking + if (request->has_uid && request->uid != geteuid()) { + if (geteuid() != 0) { + errno = EPERM; + return -EPERM; + } + } + + if (request->has_gid && request->gid != getegid()) { + if (geteuid() != 0) { + errno = EPERM; + return -EPERM; + } + } + + pid_t pid; + int saved_errno; + sigset_t oldmask; + sigset_t newmask; + + // Block all signals during fork to prevent signal handlers from running + sigfillset(&newmask); + sigprocmask(SIG_SETMASK, &newmask, &oldmask); + + pid = fork(); + saved_errno = errno; + + if (pid == 0) { + // Child process + + // Restore signal mask in child + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + // Reset signal handlers to default + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + + for (int i = 1; i < NSIG; i++) { + // Skip SIGKILL and SIGSTOP as they can't be changed + if (i == SIGKILL || i == SIGSTOP) continue; + sigaction(i, &sa, NULL); + } + + // Set up process session if detached + if (request->detached) { + setsid(); + } + + // Change directory if requested + if (request->chdir) { + if (chdir(request->chdir) != 0) { + _exit(127); + } + } + + // Apply file actions + for (size_t i = 0; i < request->actions.len; i++) { + const bun_spawn_request_file_action_t* action = &request->actions.ptr[i]; + + switch (action->type) { + case Close: + close(action->fds[0]); + break; + + case Dup2: + if (dup2(action->fds[0], action->fds[1]) < 0) { + _exit(127); + } + break; + + case Open: { + int fd = open(action->path, action->flags, action->mode); + if (fd < 0) { + _exit(127); + } + if (fd != action->fds[0]) { + if (dup2(fd, action->fds[0]) < 0) { + _exit(127); + } + close(fd); + } + break; + } + + default: + break; + } + } + + // Close all file descriptors above stderr except those we just set up + int max_fd = getdtablesize(); + for (int fd = 3; fd < max_fd; fd++) { + int flags = fcntl(fd, F_GETFD); + if (flags >= 0 && (flags & FD_CLOEXEC)) { + close(fd); + } + } + + // Set group id before user id (required order) + if (request->has_gid) { + if (setgid(request->gid) != 0) { + _exit(127); + } + } + + if (request->has_uid) { + if (setuid(request->uid) != 0) { + _exit(127); + } + } + + // Execute the program + if (!envp) { + envp = environ; + } + + execve(path, argv, envp); + + // If we get here, execve failed + _exit(127); + } + + // Parent process + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if (pid < 0) { + // Fork failed + errno = saved_errno; + return -1; + } + + return pid; +} + +#endif \ No newline at end of file