This commit is contained in:
pfg
2025-07-11 20:44:36 -07:00
parent 920947c801
commit 2d4c5cab0e
5 changed files with 264 additions and 11 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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(

View File

@@ -0,0 +1,189 @@
#include "root.h"
#if OS(DARWIN)
#include <fcntl.h>
#include <cstring>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <errno.h>
#include <stdlib.h>
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