From: Stefan Sperling Subject: -portable gotd chroot and privdrop To: gameoftrees@openbsd.org Date: Mon, 9 Nov 2026 15:40:25 +0100 Currently, gotd does not run in -portable because it ends up trying to call chroot() as a non-root user. It looks like the privdrop parts are either unfinished or have been removed at some point, since only the repo-read process even tries to drop privileges. Below is the best I could come up with in order to get gotd running on a Debian system. I assume that the user account which gotd runs as has a home directory to chroot into. This might not be the case by default on every OS but is probably a better choice than hard-coding something like "/var/empty" which will not exist everywhere either. The only process which keeps running as root is the parent. All the others are run as the gotd user account (_gotd on Debian as created by the package). Not every process can use chroot, but several can. I would merge some of this into got.git, the -U option in particular. One change I made in got.git in order to help -portable gotd turned out to be counter-productive. So I'll probably revert that change once this patch is approved and merged into -portable. ok? make gotd run properly on -portable with privdrop and chroot M gotd/chroot-notobsd.c | 1+ 3- M gotd/gotd.c | 39+ 28- M gotd/gotd.h | 1+ 1- 3 files changed, 41 insertions(+), 32 deletions(-) commit - e18a5f85e18c62a89a11d0066a46a5d1acb56ef15cfb08c7f64df771d2fb5c89 commit + 27480126341922d439ec3620855d1cad8323d88d109d11f6d76feeab9c6033d0 blob - 2d912f39049f4188fa607e318740f84a2f6baccf88cfa92a692996caef2a80b3 blob + f33feda01b5e1c724e3137fd7c201487706b17e188dcb1ed1276902030ef73b6 --- gotd/chroot-notobsd.c +++ gotd/chroot-notobsd.c @@ -22,7 +22,7 @@ #include "log.h" -int +void enter_chroot(const char *path) { log_info("chroot into %s", path); @@ -30,6 +30,4 @@ enter_chroot(const char *path) fatal("chroot"); if (chdir("/") == -1) fatal("chdir(\"/\")"); - - return 1; } blob - c5a8eeb0ec0ca9641f64f796f57c671a7f3bb7a0e093b13580c1189acfafdbe0 blob + 301afe0a5e95da3d58f9ac3d29167bcf1285472c22cc0dd5e0273934374c0d48 --- gotd/gotd.c +++ gotd/gotd.c @@ -3044,7 +3044,7 @@ start_child(enum gotd_procid proc_id, const char *repo char *argv0, const char *confpath, const char *secretspath, int fd, int daemonize, int verbosity) { - const char *argv[13]; + const char *argv[15]; int argc = 0; pid_t pid; @@ -3097,6 +3097,9 @@ start_child(enum gotd_procid proc_id, const char *repo argv[argc++] = "-f"; argv[argc++] = confpath; + argv[argc++] = "-U"; + argv[argc++] = gotd.user_name; + if (secretspath) { argv[argc++] = "-s"; argv[argc++] = secretspath; @@ -3489,7 +3492,7 @@ main(int argc, char **argv) log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */ - while ((ch = getopt_long(argc, argv, "df:nP:s:T:vV", longopts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "df:nP:s:T:U:vV", longopts, NULL)) != -1) { switch (ch) { case 'd': daemonize = 0; @@ -3540,6 +3543,11 @@ main(int argc, char **argv) errx(1, "unknown proc type %s", optarg); } break; + case 'U': + if (strlcpy(gotd.user_name, optarg, + sizeof(gotd.user_name)) >= sizeof(gotd.user_name)) + fatal("username too long: %s", optarg); + break; case 'v': if (verbosity < 3) verbosity++; @@ -3564,8 +3572,6 @@ main(int argc, char **argv) } if (geteuid() && proc_id == GOTD_PROC_GOTD) fatalx("need root privileges"); - if (geteuid() == 0 && proc_id != GOTD_PROC_GOTD) - fatalx("must not run as root"); if (proc_id == GOTD_PROC_GOTD) { const char *p = secretspath ? secretspath : GOTD_SECRETS_PATH; @@ -3750,6 +3756,15 @@ main(int argc, char **argv) close(GOTD_FILENO_MSG_PIPE); reload_reset(); + } else { + pw = getpwnam(gotd.user_name); + if (pw == NULL) { + uid = strtonum(gotd.user_name, 0, UINT_MAX - 1, &errstr); + if (errstr == NULL) + pw = getpwuid(uid); + } + if (pw == NULL) + fatalx("user %s not found", gotd.user_name); } gotd.argv0 = argv0; @@ -3804,18 +3819,10 @@ main(int argc, char **argv) event_init(); - if (proc_id == GOTD_PROC_RELOAD) + switch (proc_id) { + case GOTD_PROC_RELOAD: proc_id = GOTD_PROC_GOTD; - - if (proc_id == GOTD_PROC_GOTD) { - /* Drop root privileges. */ - if (setgid(pw->pw_gid) == -1) - fatal("setgid %d failed", pw->pw_gid); - if (setuid(pw->pw_uid) == -1) - fatal("setuid %d failed", pw->pw_uid); - } - - switch (proc_id) { + /* FALLTHOUGH */ case GOTD_PROC_GOTD: if (verbosity) { log_info("socket: %s", gotd.unix_socket_path); @@ -3843,7 +3850,8 @@ main(int argc, char **argv) * sockets by revoking all filesystem access via unveil(2). */ apply_unveil_none(); - + enter_chroot(pw->pw_dir); + drop_privs(pw); listen_main(title); /* NOTREACHED */ break; @@ -3859,7 +3867,7 @@ main(int argc, char **argv) * files will still work since "getpw" bypasses unveil(2). */ apply_unveil_none(); - + drop_privs(pw); auth_main(title); /* NOTREACHED */ break; @@ -3874,6 +3882,7 @@ main(int argc, char **argv) err(1, "pledge"); #endif apply_unveil_repo_readonly(repo_path, 1); + drop_privs(pw); session_read_main(title, repo_path, pack_fds, temp_fds); /* NOTREACHED */ break; @@ -3891,6 +3900,7 @@ main(int argc, char **argv) err(1, "pledge"); #endif apply_unveil_repo_readwrite(repo_path); + drop_privs(pw); session_write_main(title, repo_path, pack_fds, temp_fds, tmp_fd); /* NOTREACHED */ @@ -3902,17 +3912,12 @@ main(int argc, char **argv) err(1, "pledge"); #endif apply_unveil_repo_readonly(repo_path, 0); - - if (enter_chroot(repo_path)) { - log_info("change repo path %s", repo_path); - free(repo_path); - repo_path = strdup("/"); - if (repo_path == NULL) - fatal("strdup"); - log_info("repo path is now %s", repo_path); - } + enter_chroot(repo_path); + free(repo_path); + repo_path = strdup("/"); + if (repo_path == NULL) + fatal("strdup"); drop_privs(pw); - repo_read_main(title, repo_path, pack_fds, temp_fds); /* NOTREACHED */ exit(0); @@ -3942,6 +3947,12 @@ main(int argc, char **argv) err(1, "pledge"); #endif apply_unveil_repo_readonly(repo_path, 0); + enter_chroot(repo_path); + free(repo_path); + repo_path = strdup("/"); + if (repo_path == NULL) + fatal("strdup"); + drop_privs(pw); repo_write_main(title, repo_path, pack_fds, temp_fds, tmp_f1, tmp_f2, diff_f1, diff_f2, diff_fd1, diff_fd2); /* NOTREACHED */ @@ -3955,7 +3966,7 @@ main(int argc, char **argv) * Limit "exec" promise to notification helpers via unveil(2). */ unveil_notification_helpers(); - + drop_privs(pw); notify_main(title); /* NOTREACHED */ exit(0); blob - 20a4a6e93efc2cb08d25c7004bd1f74166941d0c830aaa2242fbbb5ff2404b8d blob + e825fa2b1827b5471fcf08b6629301af0f5f1ba96766e4d90ca827971433735a --- gotd/gotd.h +++ gotd/gotd.h @@ -672,7 +672,7 @@ struct gotd_imsg_notify { /* Followed by username_len data bytes. */ }; -int enter_chroot(const char *); +void enter_chroot(const char *); int gotd_parse_config(const char *, int, enum gotd_procid, struct gotd_secrets *, struct gotd *); struct gotd_repo *gotd_find_repo_by_name(const char *, struct gotd_repolist *);