diff options
author | Luis Hector Chavez <lhchavez@google.com> | 2017-09-05 09:17:22 -0700 |
---|---|---|
committer | Treehugger Robot <treehugger-gerrit@google.com> | 2017-09-14 13:16:06 +0000 |
commit | 7132355d6dd66d7e1d6736f8c662c6c59af733ca (patch) | |
tree | 7269ab810980b96b1ed901f745f37a0fdd547e5b | |
parent | daa03715de1ad4b09d9c9c3aac882a560b56e0cd (diff) | |
download | minijail-7132355d6dd66d7e1d6736f8c662c6c59af733ca.tar.gz |
Improve the way uid/gid changes in unprivileged userns
This change uses whatever was passed into the -u/-g flags as the user to change
in the user namespace. This is used to fix an issue where calling open(2) on a
file on the tmpfs created by minijail would return EOVERFLOW[1]. An easy way to
reproduce is running this on a 4.8 kernel (or Ubuntu Xenial, which has this
change backported):
$ ./minijail0 -T static -Ut -- /bin/bash -c 'touch /tmp/foo'
This change allows a non-zero uid/gid to be mapped to the current user when
entering a namespace, to avoid the above issue.
1: More information about the bug here:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1659087
Bug: None
Test: make tests
Test: ./minijail0 -T static -Ut -u 1000 -g 1000 -M -m -- \
/bin/bash -c 'touch /tmp/foo'
Change-Id: I393daaf8c2b2355e33c75a908345bb03f1980271
-rw-r--r-- | libminijail.c | 85 | ||||
-rw-r--r-- | libminijail_unittest.cc | 46 | ||||
-rw-r--r-- | minijail0.c | 174 | ||||
-rw-r--r-- | system.c | 72 | ||||
-rw-r--r-- | system.h | 3 |
5 files changed, 273 insertions, 107 deletions
diff --git a/libminijail.c b/libminijail.c index 0eca595..9915afe 100644 --- a/libminijail.c +++ b/libminijail.c @@ -13,7 +13,6 @@ #include <fcntl.h> #include <grp.h> #include <linux/capability.h> -#include <pwd.h> #include <sched.h> #include <signal.h> #include <stdbool.h> @@ -294,66 +293,26 @@ void API minijail_keep_supplementary_gids(struct minijail *j) { int API minijail_change_user(struct minijail *j, const char *user) { - char *buf = NULL; - struct passwd pw; - struct passwd *ppw = NULL; - ssize_t sz = sysconf(_SC_GETPW_R_SIZE_MAX); - if (sz == -1) - sz = 65536; /* your guess is as good as mine... */ - - /* - * sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return - * the maximum needed size of the buffer, so we don't have to search. - */ - buf = malloc(sz); - if (!buf) - return -ENOMEM; - getpwnam_r(user, &pw, buf, sz, &ppw); - /* - * We're safe to free the buffer here. The strings inside |pw| point - * inside |buf|, but we don't use any of them; this leaves the pointers - * dangling but it's safe. |ppw| points at |pw| if getpwnam_r(3) - * succeeded. - */ - free(buf); - /* getpwnam_r(3) does *not* set errno when |ppw| is NULL. */ - if (!ppw) - return -1; - minijail_change_uid(j, ppw->pw_uid); + uid_t uid; + gid_t gid; + int rc = lookup_user(user, &uid, &gid); + if (rc) + return rc; + minijail_change_uid(j, uid); j->user = strdup(user); if (!j->user) return -ENOMEM; - j->usergid = ppw->pw_gid; + j->usergid = gid; return 0; } int API minijail_change_group(struct minijail *j, const char *group) { - char *buf = NULL; - struct group gr; - struct group *pgr = NULL; - ssize_t sz = sysconf(_SC_GETGR_R_SIZE_MAX); - if (sz == -1) - sz = 65536; /* and mine is as good as yours, really */ - - /* - * sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return - * the maximum needed size of the buffer, so we don't have to search. - */ - buf = malloc(sz); - if (!buf) - return -ENOMEM; - getgrnam_r(group, &gr, buf, sz, &pgr); - /* - * We're safe to free the buffer here. The strings inside gr point - * inside buf, but we don't use any of them; this leaves the pointers - * dangling but it's safe. pgr points at gr if getgrnam_r succeeded. - */ - free(buf); - /* getgrnam_r(3) does *not* set errno when |pgr| is NULL. */ - if (!pgr) - return -1; - minijail_change_gid(j, pgr->gr_gid); + gid_t gid; + int rc = lookup_group(group, &gid); + if (rc) + return rc; + minijail_change_gid(j, gid); return 0; } @@ -1458,10 +1417,16 @@ static void write_ugid_maps_or_die(const struct minijail *j) static void enter_user_namespace(const struct minijail *j) { - if (j->uidmap && setresuid(0, 0, 0)) - pdie("user_namespaces: setresuid(0, 0, 0) failed"); - if (j->gidmap && setresgid(0, 0, 0)) - pdie("user_namespaces: setresgid(0, 0, 0) failed"); + int uid = j->flags.uid ? j->uid : 0; + int gid = j->flags.gid ? j->gid : 0; + if (j->gidmap && setresgid(gid, gid, gid)) { + pdie("user_namespaces: setresgid(%d, %d, %d) failed", gid, gid, + gid); + } + if (j->uidmap && setresuid(uid, uid, uid)) { + pdie("user_namespaces: setresuid(%d, %d, %d) failed", uid, uid, + uid); + } } static void parent_setup_complete(int *pipe_fds) @@ -1500,10 +1465,12 @@ static void drop_ugid(const struct minijail *j) } else if (j->flags.set_suppl_gids) { if (setgroups(j->suppl_gid_count, j->suppl_gid_list)) pdie("setgroups(suppl_gids) failed"); - } else if (!j->flags.keep_suppl_gids) { + } else if (!j->flags.keep_suppl_gids && !j->flags.disable_setgroups) { /* * Only attempt to clear supplementary groups if we are changing - * users or groups. + * users or groups, and if the caller did not request to disable + * setgroups (used when entering a user namespace as a + * non-privileged user). */ if ((j->flags.uid || j->flags.gid) && setgroups(0, NULL)) pdie("setgroups(0, NULL) failed"); diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc index 77775b5..93e3790 100644 --- a/libminijail_unittest.cc +++ b/libminijail_unittest.cc @@ -358,6 +358,52 @@ TEST(Test, test_minijail_preserve_fd) { minijail_destroy(j); } +TEST(Test, +#if defined(__ANDROID__) +// TODO(lhchavez): Android unit tests don't currently support entering user +// namespaces as unprivileged users. +DISABLED_test_tmpfs_userns +#else +test_tmpfs_userns +#endif +) { + int mj_run_ret; + int status; + char *argv[4]; + char uidmap[128], gidmap[128]; + constexpr uid_t kTargetUid = 1000; // Any non-zero value will do. + constexpr gid_t kTargetGid = 1000; + + struct minijail *j = minijail_new(); + + minijail_namespace_pids(j); + minijail_namespace_vfs(j); + minijail_mount_tmp(j); + minijail_run_as_init(j); + + // Perform userns mapping. + minijail_namespace_user(j); + snprintf(uidmap, sizeof(uidmap), "%d %d 1", kTargetUid, getuid()); + snprintf(gidmap, sizeof(gidmap), "%d %d 1", kTargetGid, getgid()); + minijail_change_uid(j, kTargetUid); + minijail_change_gid(j, kTargetGid); + minijail_uidmap(j, uidmap); + minijail_gidmap(j, gidmap); + minijail_namespace_user_disable_setgroups(j); + + argv[0] = (char*)kShellPath; + argv[1] = "-c"; + argv[2] = "exec touch /tmp/foo"; + argv[3] = NULL; + mj_run_ret = minijail_run_no_preload(j, argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + + status = minijail_wait(j); + EXPECT_EQ(status, 0); + + minijail_destroy(j); +} + TEST(Test, parse_size) { size_t size; diff --git a/minijail0.c b/minijail0.c index d47aad5..088bc27 100644 --- a/minijail0.c +++ b/minijail0.c @@ -8,40 +8,56 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/capability.h> +#include <sys/types.h> #include <unistd.h> #include "libminijail.h" #include "libsyscalls.h" #include "elfparse.h" +#include "system.h" #include "util.h" #define IDMAP_LEN 32U -static void set_user(struct minijail *j, const char *arg) +static void set_user(struct minijail *j, const char *arg, uid_t *out_uid, + gid_t *out_gid) { char *end = NULL; int uid = strtod(arg, &end); if (!*end && *arg) { + *out_uid = uid; minijail_change_uid(j, uid); return; } + if (lookup_user(arg, out_uid, out_gid)) { + fprintf(stderr, "Bad user: '%s'\n", arg); + exit(1); + } + if (minijail_change_user(j, arg)) { fprintf(stderr, "Bad user: '%s'\n", arg); exit(1); } } -static void set_group(struct minijail *j, const char *arg) +static void set_group(struct minijail *j, const char *arg, gid_t *out_gid) { char *end = NULL; int gid = strtod(arg, &end); if (!*end && *arg) { + *out_gid = gid; minijail_change_gid(j, gid); return; } + if (lookup_group(arg, out_gid)) { + fprintf(stderr, "Bad group: '%s'\n", arg); + exit(1); + } + if (minijail_change_group(j, arg)) { fprintf(stderr, "Bad group: '%s'\n", arg); exit(1); @@ -97,8 +113,8 @@ static void add_rlimit(struct minijail *j, char *arg) exit(1); } if (minijail_rlimit(j, atoi(type), atoi(cur), atoi(max))) { - fprintf(stderr, "minijail_rlimit '%s,%s,%s' failed.\n", - type, cur, max); + fprintf(stderr, "minijail_rlimit '%s,%s,%s' failed.\n", type, + cur, max); exit(1); } } @@ -135,6 +151,84 @@ static char *build_idmap(id_t id, id_t lowerid) return idmap; } +static int has_cap_setgid() +{ + cap_t caps; + cap_flag_value_t cap_value; + + if (!CAP_IS_SUPPORTED(CAP_SETGID)) + return 0; + + caps = cap_get_proc(); + if (!caps) { + fprintf(stderr, "Could not get process' capabilities: %m\n"); + exit(1); + } + + if (cap_get_flag(caps, CAP_SETGID, CAP_EFFECTIVE, &cap_value)) { + fprintf(stderr, "Could not get the value of CAP_SETGID: %m\n"); + exit(1); + } + + if (cap_free(caps)) { + fprintf(stderr, "Could not free capabilities: %m\n"); + exit(1); + } + + return cap_value == CAP_SET; +} + +static void set_ugid_mapping(struct minijail *j, int set_uidmap, uid_t uid, + char *uidmap, int set_gidmap, gid_t gid, + char *gidmap) +{ + if (set_uidmap) { + minijail_namespace_user(j); + minijail_namespace_pids(j); + + if (!uidmap) { + /* + * If no map is passed, map the current uid to the + * chosen uid in the target namespace (or root, if none + * was chosen). + */ + uidmap = build_idmap(uid, getuid()); + } + if (0 != minijail_uidmap(j, uidmap)) { + fprintf(stderr, "Could not set uid map.\n"); + exit(1); + } + free(uidmap); + } + if (set_gidmap) { + minijail_namespace_user(j); + minijail_namespace_pids(j); + + if (!gidmap) { + /* + * If no map is passed, map the current gid to the + * chosen gid in the target namespace. + */ + gidmap = build_idmap(gid, getgid()); + } + if (!has_cap_setgid()) { + /* + * This means that we are not running as root, + * so we also have to disable setgroups(2) to + * be able to set the gid map. + * See + * http://man7.org/linux/man-pages/man7/user_namespaces.7.html + */ + minijail_namespace_user_disable_setgroups(j); + } + if (0 != minijail_gidmap(j, gidmap)) { + fprintf(stderr, "Could not set gid map.\n"); + exit(1); + } + free(gidmap); + } +} + static void usage(const char *progn) { size_t i; @@ -242,7 +336,10 @@ static int parse_args(struct minijail *j, int argc, char *argv[], int caps = 0, ambient_caps = 0; int seccomp = -1; const size_t path_max = 4096; - char *map; + uid_t uid = 0; + gid_t gid = 0; + char *uidmap = NULL, *gidmap = NULL; + int set_uidmap = 0, set_gidmap = 0; size_t size; const char *filter_path = NULL; int log_to_stderr = 0; @@ -263,17 +360,18 @@ static int parse_args(struct minijail *j, int argc, char *argv[], &longoption_index)) != -1) { switch (opt) { case 'u': - set_user(j, optarg); + set_user(j, optarg, &uid, &gid); break; case 'g': - set_group(j, optarg); + set_group(j, optarg, &gid); break; case 'n': minijail_no_new_privs(j); break; case 's': if (seccomp != -1 && seccomp != 1) { - fprintf(stderr, "Do not use -s & -S together.\n"); + fprintf(stderr, + "Do not use -s & -S together.\n"); exit(1); } seccomp = 1; @@ -281,7 +379,8 @@ static int parse_args(struct minijail *j, int argc, char *argv[], break; case 'S': if (seccomp != -1 && seccomp != 2) { - fprintf(stderr, "Do not use -s & -S together.\n"); + fprintf(stderr, + "Do not use -s & -S together.\n"); exit(1); } seccomp = 2; @@ -419,49 +518,22 @@ static int parse_args(struct minijail *j, int argc, char *argv[], minijail_namespace_pids(j); break; case 'm': - minijail_namespace_user(j); - minijail_namespace_pids(j); - - if (optarg) { - map = strdup(optarg); - } else { - /* - * If no map is passed, map the current uid to - * root. - */ - map = build_idmap(0, getuid()); - } - if (0 != minijail_uidmap(j, map)) { - fprintf(stderr, "Could not set uid map.\n"); - exit(1); + set_uidmap = 1; + if (uidmap) { + free(uidmap); + uidmap = NULL; } - free(map); + if (optarg) + uidmap = strdup(optarg); break; case 'M': - minijail_namespace_user(j); - minijail_namespace_pids(j); - - if (optarg) { - map = strdup(optarg); - } else { - /* - * If no map is passed, map the current gid to - * root. - * This means that we're likely *not* running as - * root, so we also have to disable - * setgroups(2) to be able to set the gid map. - * See - * http://man7.org/linux/man-pages/man7/user_namespaces.7.html - */ - minijail_namespace_user_disable_setgroups(j); - - map = build_idmap(0, getgid()); - } - if (0 != minijail_gidmap(j, map)) { - fprintf(stderr, "Could not set gid map.\n"); - exit(1); + set_gidmap = 1; + if (gidmap) { + free(gidmap); + gidmap = NULL; } - free(map); + if (optarg) + gidmap = strdup(optarg); break; case 'a': if (0 != minijail_use_alt_syscall(j, optarg)) { @@ -532,6 +604,12 @@ static int parse_args(struct minijail *j, int argc, char *argv[], } } + /* Set up uid/gid mapping. */ + if (set_uidmap || set_gidmap) { + set_ugid_mapping(j, set_uidmap, uid, uidmap, set_gidmap, gid, + gidmap); + } + /* Can only set ambient caps when using regular caps. */ if (ambient_caps && !caps) { fprintf(stderr, "Can't set ambient capabilities (--ambient) " @@ -17,7 +17,9 @@ #include <errno.h> #include <fcntl.h> +#include <grp.h> #include <net/if.h> +#include <pwd.h> #include <stdbool.h> #include <stdio.h> #include <string.h> @@ -287,3 +289,73 @@ int setup_mount_destination(const char *source, const char *dest, uid_t uid, } return chown(dest, uid, gid); } + +/* + * lookup_user: Gets the uid/gid for the given username. + */ +int lookup_user(const char *user, uid_t *uid, gid_t *gid) +{ + char *buf = NULL; + struct passwd pw; + struct passwd *ppw = NULL; + ssize_t sz = sysconf(_SC_GETPW_R_SIZE_MAX); + if (sz == -1) + sz = 65536; /* your guess is as good as mine... */ + + /* + * sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return + * the maximum needed size of the buffer, so we don't have to search. + */ + buf = malloc(sz); + if (!buf) + return -ENOMEM; + getpwnam_r(user, &pw, buf, sz, &ppw); + /* + * We're safe to free the buffer here. The strings inside |pw| point + * inside |buf|, but we don't use any of them; this leaves the pointers + * dangling but it's safe. |ppw| points at |pw| if getpwnam_r(3) + * succeeded. + */ + free(buf); + /* getpwnam_r(3) does *not* set errno when |ppw| is NULL. */ + if (!ppw) + return -1; + + *uid = ppw->pw_uid; + *gid = ppw->pw_gid; + return 0; +} + +/* + * lookup_group: Gets the gid for the given group name. + */ +int lookup_group(const char *group, gid_t *gid) +{ + char *buf = NULL; + struct group gr; + struct group *pgr = NULL; + ssize_t sz = sysconf(_SC_GETGR_R_SIZE_MAX); + if (sz == -1) + sz = 65536; /* and mine is as good as yours, really */ + + /* + * sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return + * the maximum needed size of the buffer, so we don't have to search. + */ + buf = malloc(sz); + if (!buf) + return -ENOMEM; + getgrnam_r(group, &gr, buf, sz, &pgr); + /* + * We're safe to free the buffer here. The strings inside gr point + * inside buf, but we don't use any of them; this leaves the pointers + * dangling but it's safe. pgr points at gr if getgrnam_r succeeded. + */ + free(buf); + /* getgrnam_r(3) does *not* set errno when |pgr| is NULL. */ + if (!pgr) + return -1; + + *gid = pgr->gr_gid; + return 0; +} @@ -65,6 +65,9 @@ int write_proc_file(pid_t pid, const char *content, const char *basename); int setup_mount_destination(const char *source, const char *dest, uid_t uid, uid_t gid, bool bind); +int lookup_user(const char *user, uid_t *uid, gid_t *gid); +int lookup_group(const char *group, gid_t *gid); + #ifdef __cplusplus }; /* extern "C" */ #endif |