diff options
-rw-r--r-- | conf.c | 70 | ||||
-rw-r--r-- | passt.1 | 7 | ||||
-rw-r--r-- | passt.c | 46 | ||||
-rw-r--r-- | passt.h | 5 | ||||
-rw-r--r-- | util.c | 52 | ||||
-rw-r--r-- | util.h | 1 |
6 files changed, 135 insertions, 46 deletions
@@ -22,6 +22,8 @@ #include <sys/stat.h> #include <libgen.h> #include <limits.h> +#include <grp.h> +#include <pwd.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> @@ -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")) { @@ -838,6 +843,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 * @argc: Argument count @@ -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) @@ -96,6 +96,13 @@ 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. @@ -46,8 +46,6 @@ #include <sys/stat.h> #include <sys/prctl.h> #include <stddef.h> -#include <pwd.h> -#include <grp.h> #include <netinet/udp.h> #include <netinet/tcp.h> #include <netinet/if_ether.h> @@ -191,49 +189,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 * * Return: negative error code on failure, zero on success @@ -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; @@ -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; @@ -33,6 +33,8 @@ #include <string.h> #include <time.h> #include <errno.h> +#include <pwd.h> +#include <grp.h> #include <linux/capability.h> @@ -533,6 +535,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 * @@ -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); |