Handle userns isolation and dropping root at the same time
passt/pasta can interact with user namespaces in a number of ways: 1) With --netns-only we'll remain in our original user namespace 2) With --userns or a PID option to pasta we'll join either the given user namespace or that of the PID 3) When pasta spawns a shell or command we'll start a new user namespace for the command and then join it 4) With passt we'll create a new user namespace when we sandbox() ourself However (3) and (4) turn out to have essentially the same effect. In both cases we create one new user namespace. The spawned command starts there, and passt/pasta itself will live there from sandbox() onwards. Because of this, we can simplify user namespace handling by moving the userns handling earlier, to the same point we drop root in the original namespace. Extend the drop_user() function to isolate_user() which does both. After switching UID and GID in the original userns, isolate_user() will either join or create the userns we require. When we spawn a command with pasta_start_ns()/pasta_setup_ns() we no longer need to create a userns, because we're already made one. sandbox() likewise no longer needs to create (or join) an userns because we're already in the one we need. We no longer need c->pasta_userns_fd, since the fd is only used locally in isolate_user(). Likewise we can replace c->netns_only with a local in conf(), since it's not used outside there. Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
This commit is contained in:
parent
d9f889a55a
commit
eed17a47fe
8 changed files with 77 additions and 83 deletions
11
conf.c
11
conf.c
|
@ -1043,6 +1043,7 @@ static int conf_ugid(const char *runas, uid_t *uid, gid_t *gid)
|
||||||
*/
|
*/
|
||||||
void conf(struct ctx *c, int argc, char **argv)
|
void conf(struct ctx *c, int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
int netns_only = 0;
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
{"debug", no_argument, NULL, 'd' },
|
{"debug", no_argument, NULL, 'd' },
|
||||||
{"quiet", no_argument, NULL, 'q' },
|
{"quiet", no_argument, NULL, 'q' },
|
||||||
|
@ -1077,7 +1078,7 @@ void conf(struct ctx *c, int argc, char **argv)
|
||||||
{"udp-ns", required_argument, NULL, 'U' },
|
{"udp-ns", required_argument, NULL, 'U' },
|
||||||
{"userns", required_argument, NULL, 2 },
|
{"userns", required_argument, NULL, 2 },
|
||||||
{"netns", required_argument, NULL, 3 },
|
{"netns", required_argument, NULL, 3 },
|
||||||
{"netns-only", no_argument, &c->netns_only, 1 },
|
{"netns-only", no_argument, &netns_only, 1 },
|
||||||
{"config-net", no_argument, &c->pasta_conf_ns, 1 },
|
{"config-net", no_argument, &c->pasta_conf_ns, 1 },
|
||||||
{"ns-mac-addr", required_argument, NULL, 4 },
|
{"ns-mac-addr", required_argument, NULL, 4 },
|
||||||
{"dhcp-dns", no_argument, NULL, 5 },
|
{"dhcp-dns", no_argument, NULL, 5 },
|
||||||
|
@ -1515,22 +1516,22 @@ void conf(struct ctx *c, int argc, char **argv)
|
||||||
if (ret)
|
if (ret)
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
|
|
||||||
drop_root(uid, gid);
|
|
||||||
|
|
||||||
if (c->mode == MODE_PASTA) {
|
if (c->mode == MODE_PASTA) {
|
||||||
if (conf_pasta_ns(&c->netns_only, userns, netns,
|
if (conf_pasta_ns(&netns_only, userns, netns,
|
||||||
optind, argc, argv) < 0)
|
optind, argc, argv) < 0)
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
} else if (optind != argc) {
|
} else if (optind != argc) {
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isolate_user(uid, gid, !netns_only, userns);
|
||||||
|
|
||||||
if (c->pasta_conf_ns)
|
if (c->pasta_conf_ns)
|
||||||
c->no_ra = 1;
|
c->no_ra = 1;
|
||||||
|
|
||||||
if (c->mode == MODE_PASTA) {
|
if (c->mode == MODE_PASTA) {
|
||||||
if (*netns) {
|
if (*netns) {
|
||||||
pasta_open_ns(c, userns, netns);
|
pasta_open_ns(c, netns);
|
||||||
} else {
|
} else {
|
||||||
pasta_start_ns(c, argc - optind, argv + optind);
|
pasta_start_ns(c, argc - optind, argv + optind);
|
||||||
}
|
}
|
||||||
|
|
75
isolation.c
75
isolation.c
|
@ -20,6 +20,7 @@
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -59,14 +60,19 @@ void drop_caps(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* drop_root() - Switch to given UID and GID
|
* isolate_user() - Switch to final UID/GID and move into userns
|
||||||
* @uid: User ID to switch to
|
* @uid: User ID to run as (in original userns)
|
||||||
* @gid: Group ID to switch to
|
* @gid: Group ID to run as (in original userns)
|
||||||
|
* @use_userns: Whether to join or create a userns
|
||||||
|
* @userns: userns path to enter, may be empty
|
||||||
*/
|
*/
|
||||||
void drop_root(uid_t uid, gid_t gid)
|
void isolate_user(uid_t uid, gid_t gid, bool use_userns, const char *userns)
|
||||||
{
|
{
|
||||||
|
char nsmap[BUFSIZ];
|
||||||
|
|
||||||
|
/* First set our UID & GID in the original namespace */
|
||||||
if (setgroups(0, NULL)) {
|
if (setgroups(0, NULL)) {
|
||||||
/* If we don't start with CAP_SETGID, this will EPERM */
|
/* If we don't have CAP_SETGID, this will EPERM */
|
||||||
if (errno != EPERM) {
|
if (errno != EPERM) {
|
||||||
err("Can't drop supplementary groups: %s",
|
err("Can't drop supplementary groups: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
|
@ -74,11 +80,57 @@ void drop_root(uid_t uid, gid_t gid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setgid(gid) && !setuid(uid))
|
if (setgid(gid) != 0) {
|
||||||
|
err("Can't set GID to %u: %s", gid, strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setuid(uid) != 0) {
|
||||||
|
err("Can't set UID to %u: %s", uid, strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're told not to use a userns, nothing more to do */
|
||||||
|
if (!use_userns)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
err("Can't change user/group, exiting");
|
/* Otherwise, if given a userns, join it */
|
||||||
|
if (*userns) {
|
||||||
|
int ufd;
|
||||||
|
|
||||||
|
ufd = open(userns, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (ufd < 0) {
|
||||||
|
err("Couldn't open user namespace %s: %s",
|
||||||
|
userns, strerror(errno));
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setns(ufd, CLONE_NEWUSER) != 0) {
|
||||||
|
err("Couldn't enter user namespace %s: %s",
|
||||||
|
userns, strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(ufd);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, create our own userns */
|
||||||
|
if (unshare(CLONE_NEWUSER) != 0) {
|
||||||
|
err("Couldn't create user namespace: %s", strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure user and group mappings */
|
||||||
|
snprintf(nsmap, BUFSIZ, "0 %u 1", uid);
|
||||||
|
FWRITE("/proc/self/uid_map", nsmap, "Cannot set uid_map in namespace");
|
||||||
|
|
||||||
|
FWRITE("/proc/self/setgroups", "deny",
|
||||||
|
"Cannot write to setgroups in namespace");
|
||||||
|
|
||||||
|
snprintf(nsmap, BUFSIZ, "0 %u 1", gid);
|
||||||
|
FWRITE("/proc/self/gid_map", nsmap, "Cannot set gid_map in namespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,15 +142,6 @@ int sandbox(struct ctx *c)
|
||||||
{
|
{
|
||||||
int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
|
int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
|
||||||
|
|
||||||
if (!c->netns_only) {
|
|
||||||
if (c->pasta_userns_fd == -1)
|
|
||||||
flags |= CLONE_NEWUSER;
|
|
||||||
else
|
|
||||||
setns(c->pasta_userns_fd, CLONE_NEWUSER);
|
|
||||||
}
|
|
||||||
|
|
||||||
c->pasta_userns_fd = -1;
|
|
||||||
|
|
||||||
/* If we run in foreground, we have no chance to actually move to a new
|
/* If we run in foreground, we have no chance to actually move to a new
|
||||||
* PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
|
* PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
|
||||||
* ever gets around seccomp profiles -- there's no harm in passing it.
|
* ever gets around seccomp profiles -- there's no harm in passing it.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#define ISOLATION_H
|
#define ISOLATION_H
|
||||||
|
|
||||||
void drop_caps(void);
|
void drop_caps(void);
|
||||||
void drop_root(uid_t uid, gid_t gid);
|
void isolate_user(uid_t uid, gid_t gid, bool use_userns, const char *userns);
|
||||||
int sandbox(struct ctx *c);
|
int sandbox(struct ctx *c);
|
||||||
void seccomp(const struct ctx *c);
|
void seccomp(const struct ctx *c);
|
||||||
|
|
||||||
|
|
3
passt.c
3
passt.c
|
@ -23,6 +23,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
@ -185,7 +186,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
drop_caps();
|
drop_caps();
|
||||||
|
|
||||||
c.pasta_userns_fd = c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
|
c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
|
||||||
|
|
||||||
sigemptyset(&sa.sa_mask);
|
sigemptyset(&sa.sa_mask);
|
||||||
sa.sa_flags = 0;
|
sa.sa_flags = 0;
|
||||||
|
|
4
passt.h
4
passt.h
|
@ -145,8 +145,6 @@ struct ip6_ctx {
|
||||||
* @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
|
||||||
* @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
|
|
||||||
* @netns_only: In pasta mode, don't join or create a user namespace
|
|
||||||
* @no_netns_quit: In pasta mode, don't exit if fs-bound namespace is gone
|
* @no_netns_quit: In pasta mode, don't exit if fs-bound namespace is gone
|
||||||
* @netns_base: Base name for fs-bound namespace, if any, in pasta mode
|
* @netns_base: Base name for fs-bound namespace, if any, in pasta mode
|
||||||
* @netns_dir: Directory of fs-bound namespace, if any, in pasta mode
|
* @netns_dir: Directory of fs-bound namespace, if any, in pasta mode
|
||||||
|
@ -197,8 +195,6 @@ struct ctx {
|
||||||
char pid_file[PATH_MAX];
|
char pid_file[PATH_MAX];
|
||||||
|
|
||||||
int pasta_netns_fd;
|
int pasta_netns_fd;
|
||||||
int pasta_userns_fd;
|
|
||||||
int netns_only;
|
|
||||||
|
|
||||||
int no_netns_quit;
|
int no_netns_quit;
|
||||||
char netns_base[PATH_MAX];
|
char netns_base[PATH_MAX];
|
||||||
|
|
56
pasta.c
56
pasta.c
|
@ -23,6 +23,7 @@
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
|
@ -83,16 +84,6 @@ static int pasta_wait_for_ns(void *arg)
|
||||||
int flags = O_RDONLY | O_CLOEXEC;
|
int flags = O_RDONLY | O_CLOEXEC;
|
||||||
char ns[PATH_MAX];
|
char ns[PATH_MAX];
|
||||||
|
|
||||||
if (c->netns_only)
|
|
||||||
goto netns;
|
|
||||||
|
|
||||||
snprintf(ns, PATH_MAX, "/proc/%i/ns/user", pasta_child_pid);
|
|
||||||
do
|
|
||||||
while ((c->pasta_userns_fd = open(ns, flags)) < 0);
|
|
||||||
while (setns(c->pasta_userns_fd, CLONE_NEWUSER) &&
|
|
||||||
!close(c->pasta_userns_fd));
|
|
||||||
|
|
||||||
netns:
|
|
||||||
snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid);
|
snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid);
|
||||||
do
|
do
|
||||||
while ((c->pasta_netns_fd = open(ns, flags)) < 0);
|
while ((c->pasta_netns_fd = open(ns, flags)) < 0);
|
||||||
|
@ -112,25 +103,23 @@ static int ns_check(void *arg)
|
||||||
{
|
{
|
||||||
struct ctx *c = (struct ctx *)arg;
|
struct ctx *c = (struct ctx *)arg;
|
||||||
|
|
||||||
if ((!c->netns_only && setns(c->pasta_userns_fd, CLONE_NEWUSER)) ||
|
if (setns(c->pasta_netns_fd, CLONE_NEWNET))
|
||||||
setns(c->pasta_netns_fd, CLONE_NEWNET))
|
c->pasta_netns_fd = -1;
|
||||||
c->pasta_userns_fd = c->pasta_netns_fd = -1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pasta_open_ns() - Open network, user namespaces descriptors
|
* pasta_open_ns() - Open network namespace descriptors
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
* @userns: --userns argument, can be an empty string
|
|
||||||
* @netns: network namespace path
|
* @netns: network namespace path
|
||||||
*
|
*
|
||||||
* Return: 0 on success, negative error code otherwise
|
* Return: 0 on success, negative error code otherwise
|
||||||
*/
|
*/
|
||||||
void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
|
void pasta_open_ns(struct ctx *c, const char *netns)
|
||||||
{
|
{
|
||||||
int ufd = -1, nfd = -1;
|
int nfd = -1;
|
||||||
|
|
||||||
nfd = open(netns, O_RDONLY | O_CLOEXEC);
|
nfd = open(netns, O_RDONLY | O_CLOEXEC);
|
||||||
if (nfd < 0) {
|
if (nfd < 0) {
|
||||||
|
@ -138,17 +127,7 @@ void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->netns_only && *userns) {
|
|
||||||
ufd = open(userns, O_RDONLY | O_CLOEXEC);
|
|
||||||
if (ufd < 0) {
|
|
||||||
close(nfd);
|
|
||||||
err("Couldn't open user namespace %s", userns);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c->pasta_netns_fd = nfd;
|
c->pasta_netns_fd = nfd;
|
||||||
c->pasta_userns_fd = ufd;
|
|
||||||
|
|
||||||
NS_CALL(ns_check, c);
|
NS_CALL(ns_check, c);
|
||||||
|
|
||||||
|
@ -169,12 +148,9 @@ void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct pasta_setup_ns_arg - Argument for pasta_setup_ns()
|
* struct pasta_setup_ns_arg - Argument for pasta_setup_ns()
|
||||||
* @c: Execution context
|
* @argv: Command and arguments to run
|
||||||
* @euid: Effective UID of caller
|
|
||||||
*/
|
*/
|
||||||
struct pasta_setup_ns_arg {
|
struct pasta_setup_ns_arg {
|
||||||
struct ctx *c;
|
|
||||||
int euid;
|
|
||||||
char **argv;
|
char **argv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,21 +164,6 @@ static int pasta_setup_ns(void *arg)
|
||||||
{
|
{
|
||||||
struct pasta_setup_ns_arg *a = (struct pasta_setup_ns_arg *)arg;
|
struct pasta_setup_ns_arg *a = (struct pasta_setup_ns_arg *)arg;
|
||||||
|
|
||||||
if (!a->c->netns_only) {
|
|
||||||
char buf[BUFSIZ];
|
|
||||||
|
|
||||||
snprintf(buf, BUFSIZ, "%i %i %i", 0, a->euid, 1);
|
|
||||||
|
|
||||||
FWRITE("/proc/self/uid_map", buf,
|
|
||||||
"Cannot set uid_map in namespace");
|
|
||||||
|
|
||||||
FWRITE("/proc/self/setgroups", "deny",
|
|
||||||
"Cannot write to setgroups in namespace");
|
|
||||||
|
|
||||||
FWRITE("/proc/self/gid_map", buf,
|
|
||||||
"Cannot set gid_map in namespace");
|
|
||||||
}
|
|
||||||
|
|
||||||
FWRITE("/proc/sys/net/ipv4/ping_group_range", "0 0",
|
FWRITE("/proc/sys/net/ipv4/ping_group_range", "0 0",
|
||||||
"Cannot set ping_group_range, ICMP requests might fail");
|
"Cannot set ping_group_range, ICMP requests might fail");
|
||||||
|
|
||||||
|
@ -221,8 +182,6 @@ static int pasta_setup_ns(void *arg)
|
||||||
void pasta_start_ns(struct ctx *c, int argc, char *argv[])
|
void pasta_start_ns(struct ctx *c, int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct pasta_setup_ns_arg arg = {
|
struct pasta_setup_ns_arg arg = {
|
||||||
.c = c,
|
|
||||||
.euid = geteuid(),
|
|
||||||
.argv = argv,
|
.argv = argv,
|
||||||
};
|
};
|
||||||
char *shell = getenv("SHELL") ? getenv("SHELL") : "/bin/sh";
|
char *shell = getenv("SHELL") ? getenv("SHELL") : "/bin/sh";
|
||||||
|
@ -244,7 +203,6 @@ void pasta_start_ns(struct ctx *c, int argc, char *argv[])
|
||||||
|
|
||||||
pasta_child_pid = clone(pasta_setup_ns,
|
pasta_child_pid = clone(pasta_setup_ns,
|
||||||
ns_fn_stack + sizeof(ns_fn_stack) / 2,
|
ns_fn_stack + sizeof(ns_fn_stack) / 2,
|
||||||
(c->netns_only ? 0 : CLONE_NEWUSER) |
|
|
||||||
CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
|
CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
|
||||||
CLONE_NEWUTS,
|
CLONE_NEWUTS,
|
||||||
(void *)&arg);
|
(void *)&arg);
|
||||||
|
|
2
pasta.h
2
pasta.h
|
@ -6,7 +6,7 @@
|
||||||
#ifndef PASTA_H
|
#ifndef PASTA_H
|
||||||
#define PASTA_H
|
#define PASTA_H
|
||||||
|
|
||||||
void pasta_open_ns(struct ctx *c, const char *userns, const char *netns);
|
void pasta_open_ns(struct ctx *c, const char *netns);
|
||||||
void pasta_start_ns(struct ctx *c, int argc, char *argv[]);
|
void pasta_start_ns(struct ctx *c, int argc, char *argv[]);
|
||||||
void pasta_ns_conf(struct ctx *c);
|
void pasta_ns_conf(struct ctx *c);
|
||||||
void pasta_child_handler(int signal);
|
void pasta_child_handler(int signal);
|
||||||
|
|
5
util.c
5
util.c
|
@ -464,11 +464,6 @@ void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
|
||||||
*/
|
*/
|
||||||
int ns_enter(const struct ctx *c)
|
int ns_enter(const struct ctx *c)
|
||||||
{
|
{
|
||||||
if (!c->netns_only &&
|
|
||||||
c->pasta_userns_fd != -1 &&
|
|
||||||
setns(c->pasta_userns_fd, CLONE_NEWUSER))
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
if (setns(c->pasta_netns_fd, CLONE_NEWNET))
|
if (setns(c->pasta_netns_fd, CLONE_NEWNET))
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue