]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
unshare: add --setgroups=deny|allow
authorKarel Zak <kzak@redhat.com>
Thu, 8 Jan 2015 10:51:58 +0000 (11:51 +0100)
committerKarel Zak <kzak@redhat.com>
Fri, 9 Jan 2015 09:35:16 +0000 (10:35 +0100)
Since Linux 3.19 the file /proc/self/setgroups controls setgroups(2)
syscall usage in user namespaces. This patch provides command line knob
for this feature.

The new --setgroups does not automatically implies --user to avoid
complexity, it's user's responsibility to use it in right context. The
exception is --map-root-user which is mutually exclusive to
--setgroups=allow.

CC: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
sys-utils/unshare.1
sys-utils/unshare.c

index 1aa9bcb5060b346013f1e2bd2e7e3936e573d85e..c9e159d962522465d1dad7440de2cc4970a1ea21 100644 (file)
@@ -85,6 +85,21 @@ conveniently gain capabilities needed to manage various aspects of the newly cre
 namespaces (such as configuring interfaces in the network namespace or mounting filesystems in
 the mount namespace) even when run unprivileged.  As a mere convenience feature, it does not support
 more sophisticated use cases, such as mapping multiple ranges of UIDs and GIDs.
+This option implies --setgroups=deny.
+.TP
+.BR \-s , " \-\-setgroups \fIallow|deny\fP"
+Allow or deny
+.BR setgroups (2)
+syscall in user namespaces.
+
+.BR setgroups(2)
+is only callable with CAP_SETGID and CAP_SETGID in a user
+namespace (since Linux 3.19) does not give you permission to call setgroups(2)
+until after GID map has been set. The GID map is writable by root when
+.BR setgroups(2)
+is enabled and GID map becomes writable by unprivileged processes when
+.BR setgroups(2)
+is permamently disabled.
 .TP
 .BR \-V , " \-\-version"
 Display version information and exit.
index 9fdce931f385cdd2ae0603675e81fdc54cbb99ac..83c4a005998524547c3e7e05c975c34f0a741307 100644 (file)
 #include "pathnames.h"
 #include "all-io.h"
 
-static void disable_setgroups(void)
+enum {
+       SETGROUPS_NONE = -1,
+       SETGROUPS_DENY = 0,
+       SETGROUPS_ALLOW = 1,
+};
+
+static const char *setgroups_strings[] =
+{
+       [SETGROUPS_DENY] = "deny",
+       [SETGROUPS_ALLOW] = "allow"
+};
+
+static int setgroups_str2id(const char *str)
+{
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
+               if (strcmp(str, setgroups_strings[i]) == 0)
+                       return i;
+
+       errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
+}
+
+static void setgroups_control(int action)
 {
        const char *file = _PATH_PROC_SETGROUPS;
-       const char *deny = "deny";
+       const char *cmd;
        int fd;
 
+       if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
+               return;
+       cmd = setgroups_strings[action];
+
        fd = open(file, O_WRONLY);
        if (fd < 0) {
                if (errno == ENOENT)
@@ -52,7 +79,7 @@ static void disable_setgroups(void)
                 err(EXIT_FAILURE, _("cannot open %s"), file);
        }
 
-       if (write_all(fd, deny, strlen(deny)))
+       if (write_all(fd, cmd, strlen(cmd)))
                err(EXIT_FAILURE, _("write failed %s"), file);
        close(fd);
 }
@@ -94,6 +121,7 @@ static void usage(int status)
        fputs(_(" -f, --fork                fork before launching <program>\n"), out);
        fputs(_("     --mount-proc[=<dir>]  mount proc filesystem first (implies --mount)\n"), out);
        fputs(_(" -r, --map-root-user       map current user to root (implies --user)\n"), out);
+       fputs(_(" -s, --setgroups <allow|deny>  control setgroups syscall in user namespaces\n"), out);
 
        fputs(USAGE_SEPARATOR, out);
        fputs(USAGE_HELP, out);
@@ -106,7 +134,8 @@ static void usage(int status)
 int main(int argc, char *argv[])
 {
        enum {
-               OPT_MOUNTPROC = CHAR_MAX + 1
+               OPT_MOUNTPROC = CHAR_MAX + 1,
+               OPT_SETGROUPS
        };
        static const struct option longopts[] = {
                { "help", no_argument, 0, 'h' },
@@ -120,9 +149,11 @@ int main(int argc, char *argv[])
                { "fork", no_argument, 0, 'f' },
                { "mount-proc", optional_argument, 0, OPT_MOUNTPROC },
                { "map-root-user", no_argument, 0, 'r' },
+               { "setgroups", required_argument, 0, OPT_SETGROUPS },
                { NULL, 0, 0, 0 }
        };
 
+       int setgrpcmd = SETGROUPS_NONE;
        int unshare_flags = 0;
        int c, forkit = 0, maproot = 0;
        const char *procmnt = NULL;
@@ -170,6 +201,9 @@ int main(int argc, char *argv[])
                        unshare_flags |= CLONE_NEWUSER;
                        maproot = 1;
                        break;
+               case OPT_SETGROUPS:
+                       setgrpcmd = setgroups_str2id(optarg);
+                       break;
                default:
                        usage(EXIT_FAILURE);
                }
@@ -199,10 +233,20 @@ int main(int argc, char *argv[])
        }
 
        if (maproot) {
-               disable_setgroups();
+               if (setgrpcmd == SETGROUPS_ALLOW)
+                       errx(EXIT_FAILURE, _("options --setgroups=allow and "
+                                       "--map-root-user are mutually exclusive."));
+
+               /* since Linux 3.19 unprivileged writing of /proc/self/gid_map
+                * has s been disabled unless /proc/self/setgroups is written
+                * first to permanently disable the ability to call setgroups
+                * in that user namespace. */
+               setgroups_control(SETGROUPS_DENY);
                map_id(_PATH_PROC_UIDMAP, 0, real_euid);
                map_id(_PATH_PROC_GIDMAP, 0, real_egid);
-       }
+
+       } else if (setgrpcmd != SETGROUPS_NONE)
+               setgroups_control(setgrpcmd);
 
        if (procmnt &&
            (mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0 ||