From 3c1772d478ab2f1a1684d8098a6219bb00af1939 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 15 Aug 2026 10:23:20 +0200 Subject: [PATCH] Move user and group resolution before fork for async-signal safety --- os/unix.c | 150 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 50 deletions(-) diff --git a/os/unix.c b/os/unix.c index d1309f46..9311fa64 100644 --- a/os/unix.c +++ b/os/unix.c @@ -742,6 +742,74 @@ int os_validate_exec_input(const char* str) return 0; } +/****************************************************************************/ +/* user credential management */ + +#define GROUPS_MAX 64 +#define PASSWD_MAX 2048 + +struct os_user { + int enabled; + uid_t uid; + gid_t gid; + int ngroups; + gid_t groups[GROUPS_MAX]; +}; + +#ifdef __APPLE__ +typedef int os_group_t; +#else +typedef gid_t os_group_t; +#endif + +static int os_user_lookup(const char* run_as_user, struct os_user* user) +{ + memset(user, 0, sizeof(*user)); + + if (!run_as_user || run_as_user[0] == 0) + return 0; + + struct passwd pw; + struct passwd* pw_res = 0; + char pw_buf[PASSWD_MAX]; + int err = getpwnam_r(run_as_user, &pw, pw_buf, sizeof(pw_buf), &pw_res); + if (err != 0 || !pw_res) { + os_syslog(OS_LVL_INFO, "failed to get user info for %s, errno=%s(%d)", run_as_user, strerror(err ? err : ENOENT), err ? err : ENOENT); + return -1; + } + + user->enabled = 1; + user->uid = pw.pw_uid; + user->gid = pw.pw_gid; + + os_group_t groups_buf[GROUPS_MAX]; + int ngroups = sizeof(groups_buf) / sizeof(groups_buf[0]); + + if (getgrouplist(pw.pw_name, (os_group_t)pw.pw_gid, groups_buf, &ngroups) < 0) { + os_syslog(OS_LVL_INFO, "failed to get group list for %s, exceeds maximum of %d groups", run_as_user, GROUPS_MAX); + return -1; + } + + user->ngroups = ngroups; + for (int i = 0; i < ngroups; ++i) { + user->groups[i] = (gid_t)groups_buf[i]; + } + + return 0; +} + +static void os_user_drop_privileges(const struct os_user* user) +{ + if (user->enabled) { + if (setgroups(user->ngroups, user->groups) != 0) + _exit(126); + if (setgid(user->gid) != 0) + _exit(126); + if (setuid(user->uid) != 0) + _exit(126); + } +} + /** * Executes a script directly via its file descriptor. */ @@ -752,6 +820,7 @@ int os_script(char** argv, char** envp, const char* run_as_user) int ret; int status; int64_t start, stop; + struct os_user user; int fd = verify_executable(argv[0], resolved_path, 1); if (fd < 0) { @@ -763,6 +832,11 @@ int os_script(char** argv, char** envp, const char* run_as_user) return -1; } + if (os_user_lookup(run_as_user, &user) != 0) { + close(fd); + return -1; + } + start = os_tick_sec(); #if defined(__linux__) && defined(PR_SET_PDEATHSIG) @@ -788,23 +862,7 @@ int os_script(char** argv, char** envp, const char* run_as_user) setpgid(0, 0); /* drop privileges first (if configured) */ - if (run_as_user && run_as_user[0] != 0) { - errno = 0; - struct passwd* pw = getpwnam(run_as_user); - if (!pw) { - /* if errno is 0, user simply wasn't found. Otherwise, it's a real error */ - if (errno == 0) - _exit(127); - else - _exit(126); - } - if (initgroups(pw->pw_name, pw->pw_gid) != 0) - _exit(126); - if (setgid(pw->pw_gid) != 0) - _exit(126); - if (setuid(pw->pw_uid) != 0) - _exit(126); - } + os_user_drop_privileges(&user); #if defined(__linux__) && defined(PR_SET_PDEATHSIG) /* Send SIGKILL to child if parent daemon dies unexpectedly */ @@ -938,6 +996,7 @@ int os_command(const char* command, const char* run_as_user, const char* stdin_t int status; int pipe_fds[2] = { -1, -1 }; int64_t start, stop; + struct os_user user; /* create pipe only if we have text to send */ if (stdin_text != NULL) { @@ -947,6 +1006,14 @@ int os_command(const char* command, const char* run_as_user, const char* stdin_t } } + if (os_user_lookup(run_as_user, &user) != 0) { + if (pipe_fds[0] != -1) { + close(pipe_fds[0]); + close(pipe_fds[1]); + } + return -1; + } + start = os_tick_sec(); #if defined(__linux__) && defined(PR_SET_PDEATHSIG) @@ -978,23 +1045,7 @@ int os_command(const char* command, const char* run_as_user, const char* stdin_t close(pipe_fds[1]); /* Close unused write end */ /* drop privileges first (if configured) */ - if (run_as_user && run_as_user[0] != 0) { - errno = 0; - struct passwd* pw = getpwnam(run_as_user); - if (!pw) { - /* if errno is 0, user simply wasn't found. Otherwise, it's a real error */ - if (errno == 0) - _exit(127); - else - _exit(126); - } - if (initgroups(pw->pw_name, pw->pw_gid) != 0) - _exit(126); - if (setgid(pw->pw_gid) != 0) - _exit(126); - if (setuid(pw->pw_uid) != 0) - _exit(126); - } + os_user_drop_privileges(&user); #if defined(__linux__) && defined(PR_SET_PDEATHSIG) /* Send SIGKILL to child if parent daemon dies unexpectedly */ @@ -1160,6 +1211,7 @@ pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char int has_out = (stdout_read_fd != NULL); int has_err = (stderr_read_fd != NULL); pid_t pid; + struct os_user user; int fd = verify_executable(argv[0], resolved_path, 0); if (fd < 0) { @@ -1186,6 +1238,19 @@ pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char } } + if (os_user_lookup(run_as_user, &user) != 0) { + if (has_out) { + close(out_pipe[0]); + close(out_pipe[1]); + } + if (has_err) { + close(err_pipe[0]); + close(err_pipe[1]); + } + close(fd); + return -1; + } + #if defined(__linux__) && defined(PR_SET_PDEATHSIG) pid_t ppid = getpid(); #endif @@ -1211,22 +1276,7 @@ pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char setpgid(0, 0); /* drop privileges first (if configured) */ - if (run_as_user && run_as_user[0] != 0) { - errno = 0; - struct passwd* pw = getpwnam(run_as_user); - if (!pw) { - if (errno == 0) - _exit(127); - else - _exit(126); - } - if (initgroups(pw->pw_name, pw->pw_gid) != 0) - _exit(126); - if (setgid(pw->pw_gid) != 0) - _exit(126); - if (setuid(pw->pw_uid) != 0) - _exit(126); - } + os_user_drop_privileges(&user); #if defined(__linux__) && defined(PR_SET_PDEATHSIG) /* Send SIGKILL to child if parent daemon dies unexpectedly */