conf: Add --runas option, changing to given UID and GID if started as root
On some systems, user and group "nobody" might not be available. The new --runas option allows to override the default "nobody" choice if started as root. Now that we allow this, drop the initgroups() call that was used to add any additional groups for the given user, as that might now grant unnecessarily broad permissions. For instance, several distributions have a "kvm" group to allow regular user access to /dev/kvm, and we don't need that in passt or pasta. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
parent
c318ffcb4c
commit
a951e0b9ef
6 changed files with 135 additions and 46 deletions
70
conf.c
70
conf.c
|
@ -22,6 +22,8 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <pwd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.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( " default: run in background if started from a TTY");
|
||||||
info( " -e, --stderr Log to stderr too");
|
info( " -e, --stderr Log to stderr too");
|
||||||
info( " default: log to system logger only if started from a TTY");
|
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");
|
info( " -h, --help Display this help message and exit");
|
||||||
|
|
||||||
if (strstr(name, "pasta")) {
|
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
|
* conf() - Process command-line arguments and set configuration
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
|
@ -889,6 +945,7 @@ void conf(struct ctx *c, int argc, char **argv)
|
||||||
{"dns-forward", required_argument, NULL, 9 },
|
{"dns-forward", required_argument, NULL, 9 },
|
||||||
{"no-netns-quit", no_argument, NULL, 10 },
|
{"no-netns-quit", no_argument, NULL, 10 },
|
||||||
{"trace", no_argument, NULL, 11 },
|
{"trace", no_argument, NULL, 11 },
|
||||||
|
{"runas", required_argument, NULL, 12 },
|
||||||
{ 0 },
|
{ 0 },
|
||||||
};
|
};
|
||||||
struct get_bound_ports_ns_arg ns_ports_arg = { .c = c };
|
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;
|
c->trace = c->debug = c->foreground = 1;
|
||||||
break;
|
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':
|
case 'd':
|
||||||
if (c->debug) {
|
if (c->debug) {
|
||||||
err("Multiple --debug options given");
|
err("Multiple --debug options given");
|
||||||
|
@ -1298,6 +1366,8 @@ void conf(struct ctx *c, int argc, char **argv)
|
||||||
}
|
}
|
||||||
} while (name != -1);
|
} while (name != -1);
|
||||||
|
|
||||||
|
check_root(c);
|
||||||
|
|
||||||
if (c->mode == MODE_PASTA && optind + 1 == argc) {
|
if (c->mode == MODE_PASTA && optind + 1 == argc) {
|
||||||
ret = conf_ns_opt(c, nsdir, userns, argv[optind]);
|
ret = conf_ns_opt(c, nsdir, userns, argv[optind]);
|
||||||
if (ret == -ENOENT)
|
if (ret == -ENOENT)
|
||||||
|
|
7
passt.1
7
passt.1
|
@ -95,6 +95,13 @@ Log to standard error too.
|
||||||
Default is to log to system logger only, if started from an interactive
|
Default is to log to system logger only, if started from an interactive
|
||||||
terminal, and to both system logger and standard error otherwise.
|
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
|
.TP
|
||||||
.BR \-h ", " \-\-help
|
.BR \-h ", " \-\-help
|
||||||
Display a help message and exit.
|
Display a help message and exit.
|
||||||
|
|
46
passt.c
46
passt.c
|
@ -46,8 +46,6 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <pwd.h>
|
|
||||||
#include <grp.h>
|
|
||||||
#include <netinet/udp.h>
|
#include <netinet/udp.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <netinet/if_ether.h>
|
#include <netinet/if_ether.h>
|
||||||
|
@ -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
|
* 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);
|
arch_avx2_exec(argv);
|
||||||
|
|
||||||
check_root();
|
|
||||||
drop_caps();
|
drop_caps();
|
||||||
|
|
||||||
c.pasta_userns_fd = c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
|
c.pasta_userns_fd = c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
|
||||||
|
|
5
passt.h
5
passt.h
|
@ -106,6 +106,8 @@ enum passt_modes {
|
||||||
* @sock_path: Path for UNIX domain socket
|
* @sock_path: Path for UNIX domain socket
|
||||||
* @pcap: Path for packet capture file
|
* @pcap: Path for packet capture file
|
||||||
* @pid_file: Path to PID file, empty string if not configured
|
* @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_netns_fd: File descriptor for network namespace in pasta mode
|
||||||
* @pasta_userns_fd: Descriptor for user namespace to join, -1 once joined
|
* @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
|
* @netns_only: In pasta mode, don't join or create a user namespace
|
||||||
|
@ -170,6 +172,9 @@ struct ctx {
|
||||||
char pcap[PATH_MAX];
|
char pcap[PATH_MAX];
|
||||||
char pid_file[PATH_MAX];
|
char pid_file[PATH_MAX];
|
||||||
|
|
||||||
|
uid_t uid;
|
||||||
|
uid_t gid;
|
||||||
|
|
||||||
int pasta_netns_fd;
|
int pasta_netns_fd;
|
||||||
int pasta_userns_fd;
|
int pasta_userns_fd;
|
||||||
int netns_only;
|
int netns_only;
|
||||||
|
|
52
util.c
52
util.c
|
@ -33,6 +33,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <grp.h>
|
||||||
|
|
||||||
#include <linux/capability.h>
|
#include <linux/capability.h>
|
||||||
|
|
||||||
|
@ -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
|
* ns_enter() - Enter configured user (unless already joined) and network ns
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
|
|
1
util.h
1
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,
|
void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
|
||||||
uint8_t *map, uint8_t *exclude);
|
uint8_t *map, uint8_t *exclude);
|
||||||
void drop_caps(void);
|
void drop_caps(void);
|
||||||
|
void check_root(struct ctx *c);
|
||||||
int ns_enter(const struct ctx *c);
|
int ns_enter(const struct ctx *c);
|
||||||
void write_pidfile(int fd, pid_t pid);
|
void write_pidfile(int fd, pid_t pid);
|
||||||
int __daemon(int pidfile_fd, int devnull_fd);
|
int __daemon(int pidfile_fd, int devnull_fd);
|
||||||
|
|
Loading…
Reference in a new issue