aboutgitcodebugslistschat
diff options
context:
space:
mode:
-rw-r--r--conf.c70
-rw-r--r--passt.17
-rw-r--r--passt.c46
-rw-r--r--passt.h5
-rw-r--r--util.c52
-rw-r--r--util.h1
6 files changed, 135 insertions, 46 deletions
diff --git a/conf.c b/conf.c
index 0baf4fa..ddad9a3 100644
--- a/conf.c
+++ b/conf.c
@@ -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)
diff --git a/passt.1 b/passt.1
index cdca3e9..d3af916 100644
--- a/passt.1
+++ b/passt.1
@@ -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.
diff --git a/passt.c b/passt.c
index e5064f8..dd0229a 100644
--- a/passt.c
+++ b/passt.c
@@ -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;
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 <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
*
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);