diff --git a/conf.c b/conf.c index 0baf4fa..ddad9a3 100644 --- a/conf.c +++ b/conf.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -614,6 +616,9 @@ static void usage(const char *name) info( " default: run in background if started from a TTY"); info( " -e, --stderr Log to stderr too"); info( " default: log to system logger only if started from a TTY"); + info( " --runas UID|UID:GID Use given UID, GID if started as root"); + info( " UID and GID can be numeric, or login and group names"); + info( " default: drop to user \"nobody\""); info( " -h, --help Display this help message and exit"); if (strstr(name, "pasta")) { @@ -837,6 +842,57 @@ dns6: } } +/** + * conf_runas() - Handle --runas: look up desired UID and GID + * @opt: Passed option value + * @uid: User ID, set on return if valid + * @gid: Group ID, set on return if valid + * + * Return: 0 on success, negative error code on failure + */ +static int conf_runas(const char *opt, unsigned int *uid, unsigned int *gid) +{ + char ubuf[LOGIN_NAME_MAX], gbuf[LOGIN_NAME_MAX], *endptr; + struct passwd *pw; + struct group *gr; + + /* NOLINTNEXTLINE(cert-err34-c): 2 if conversion succeeds */ + if (sscanf(opt, "%u:%u", uid, gid) == 2 && uid && gid) + return 0; + + *uid = strtol(opt, &endptr, 0); + if (!*endptr && (*gid = *uid)) + return 0; + +#ifdef GLIBC_NO_STATIC_NSS + (void)ubuf; + (void)gbuf; + (void)pw; + + return -EINVAL; +#else + /* NOLINTNEXTLINE(cert-err34-c): 2 if conversion succeeds */ + if (sscanf(opt, "%" STR(LOGIN_NAME_MAX) "[^:]:" + "%" STR(LOGIN_NAME_MAX) "s", ubuf, gbuf) == 2) { + pw = getpwnam(ubuf); + if (!pw || !(*uid = pw->pw_uid)) + return -ENOENT; + + gr = getgrnam(gbuf); + if (!gr || !(*gid = gr->gr_gid)) + return -ENOENT; + + return 0; + } + + pw = getpwnam(ubuf); + if (!pw || !(*uid = pw->pw_uid) || !(*gid = pw->pw_gid)) + return -ENOENT; + + return 0; +#endif /* !GLIBC_NO_STATIC_NSS */ +} + /** * conf() - Process command-line arguments and set configuration * @c: Execution context @@ -889,6 +945,7 @@ void conf(struct ctx *c, int argc, char **argv) {"dns-forward", required_argument, NULL, 9 }, {"no-netns-quit", no_argument, NULL, 10 }, {"trace", no_argument, NULL, 11 }, + {"runas", required_argument, NULL, 12 }, { 0 }, }; struct get_bound_ports_ns_arg ns_ports_arg = { .c = c }; @@ -1032,6 +1089,17 @@ void conf(struct ctx *c, int argc, char **argv) c->trace = c->debug = c->foreground = 1; break; + case 12: + if (c->uid || c->gid) { + err("Multiple --runas options given"); + usage(argv[0]); + } + + if (conf_runas(optarg, &c->uid, &c->gid)) { + err("Invalid --runas option: %s", optarg); + usage(argv[0]); + } + break; case 'd': if (c->debug) { err("Multiple --debug options given"); @@ -1298,6 +1366,8 @@ void conf(struct ctx *c, int argc, char **argv) } } while (name != -1); + check_root(c); + if (c->mode == MODE_PASTA && optind + 1 == argc) { ret = conf_ns_opt(c, nsdir, userns, argv[optind]); if (ret == -ENOENT) diff --git a/passt.1 b/passt.1 index cdca3e9..d3af916 100644 --- a/passt.1 +++ b/passt.1 @@ -95,6 +95,13 @@ Log to standard error too. Default is to log to system logger only, if started from an interactive terminal, and to both system logger and standard error otherwise. +.TP +.BR \-\-runas " " \fIUID\fR|\fIUID:GID\fR|\fILOGIN\fR|\fILOGIN:GROUP\fR +If started as root, change to given UID and corresponding group if UID is given, +or to given UID and given GID if both are given. Alternatively, login name, or +login name and group name can be passed. +Default is to change to user \fInobody\fR if started as root. + .TP .BR \-h ", " \-\-help Display a help message and exit. diff --git a/passt.c b/passt.c index e5064f8..dd0229a 100644 --- a/passt.c +++ b/passt.c @@ -46,8 +46,6 @@ #include #include #include -#include -#include #include #include #include @@ -190,49 +188,6 @@ static void seccomp(const struct ctx *c) } } -/** - * check_root() - Warn if root in init, exit if we can't drop to nobody - */ -static void check_root(void) -{ - const char root_uid_map[] = " 0 0 4294967295"; - struct passwd *pw; - char buf[BUFSIZ]; - int fd; - - if (getuid() && geteuid()) - return; - - if ((fd = open("/proc/self/uid_map", O_RDONLY | O_CLOEXEC)) < 0) - return; - - if (read(fd, buf, BUFSIZ) != sizeof(root_uid_map) || - strncmp(buf, root_uid_map, sizeof(root_uid_map) - 1)) { - close(fd); - return; - } - - close(fd); - - fprintf(stderr, "Don't run this as root. Changing to nobody...\n"); -#ifndef GLIBC_NO_STATIC_NSS - pw = getpwnam("nobody"); - if (!pw) { - perror("getpwnam"); - exit(EXIT_FAILURE); - } - - if (!initgroups(pw->pw_name, pw->pw_gid) && - !setgid(pw->pw_gid) && !setuid(pw->pw_uid)) - return; -#else - (void)pw; -#endif - - fprintf(stderr, "Can't change to user/group nobody, exiting"); - exit(EXIT_FAILURE); -} - /** * sandbox() - Unshare IPC, mount, PID, UTS, and user namespaces, "unmount" root * @@ -336,7 +291,6 @@ int main(int argc, char **argv) arch_avx2_exec(argv); - check_root(); drop_caps(); c.pasta_userns_fd = c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1; diff --git a/passt.h b/passt.h index 69e334d..e541341 100644 --- a/passt.h +++ b/passt.h @@ -106,6 +106,8 @@ enum passt_modes { * @sock_path: Path for UNIX domain socket * @pcap: Path for packet capture file * @pid_file: Path to PID file, empty string if not configured + * @uid: UID we should drop to, if started as root + * @gid: GID we should drop to, if started as root * @pasta_netns_fd: File descriptor for network namespace in pasta mode * @pasta_userns_fd: Descriptor for user namespace to join, -1 once joined * @netns_only: In pasta mode, don't join or create a user namespace @@ -170,6 +172,9 @@ struct ctx { char pcap[PATH_MAX]; char pid_file[PATH_MAX]; + uid_t uid; + uid_t gid; + int pasta_netns_fd; int pasta_userns_fd; int netns_only; diff --git a/util.c b/util.c index 9afd2a5..7ffd9d1 100644 --- a/util.c +++ b/util.c @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include @@ -532,6 +534,56 @@ void drop_caps(void) } } +/** + * check_root() - Check if root in init ns, exit if we can't drop to user + */ +void check_root(struct ctx *c) +{ + const char root_uid_map[] = " 0 0 4294967295"; + struct passwd *pw; + char buf[BUFSIZ]; + int fd; + + if (getuid() && geteuid()) + return; + + if ((fd = open("/proc/self/uid_map", O_RDONLY | O_CLOEXEC)) < 0) + return; + + if (read(fd, buf, BUFSIZ) != sizeof(root_uid_map) || + strncmp(buf, root_uid_map, sizeof(root_uid_map) - 1)) { + close(fd); + return; + } + + close(fd); + + if (!c->uid) { + fprintf(stderr, "Don't run as root. Changing to nobody...\n"); +#ifndef GLIBC_NO_STATIC_NSS + pw = getpwnam("nobody"); + if (!pw) { + perror("getpwnam"); + exit(EXIT_FAILURE); + } + + c->uid = pw->pw_uid; + c->gid = pw->pw_gid; +#else + (void)pw; + + /* Common value for 'nobody', not really specified */ + c->uid = c->gid = 65534; +#endif + } + + if (!setgid(c->gid) && !setuid(c->uid)) + return; + + fprintf(stderr, "Can't change user/group, exiting"); + exit(EXIT_FAILURE); +} + /** * ns_enter() - Enter configured user (unless already joined) and network ns * @c: Execution context diff --git a/util.h b/util.h index 6f2c702..ae4cc54 100644 --- a/util.h +++ b/util.h @@ -240,6 +240,7 @@ char *line_read(char *buf, size_t len, int fd); void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns, uint8_t *map, uint8_t *exclude); void drop_caps(void); +void check_root(struct ctx *c); int ns_enter(const struct ctx *c); void write_pidfile(int fd, pid_t pid); int __daemon(int pidfile_fd, int devnull_fd);