]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
unshare: add --keep-caps option
authorJames Peach <jpeach@apache.org>
Thu, 17 Jan 2019 22:16:54 +0000 (14:16 -0800)
committerJames Peach <jpeach@apache.org>
Tue, 10 Sep 2019 03:23:08 +0000 (20:23 -0700)
Add the --keep-caps option to unshare to preserve capabilities that
are granted when creating a new user namespace. This allows the child
process to retain privilege within the new user namespace without also
being UID 0.

include/Makemodule.am
include/caputils.h [new file with mode: 0644]
lib/Makemodule.am
lib/caputils.c [new file with mode: 0644]
sys-utils/setpriv.c
sys-utils/unshare.1
sys-utils/unshare.c

index 3e7f7a01efcba00c48ad02b6b3c3764b136e4604..08f0e997ee7f9447f20e047137d706c63428747f 100644 (file)
@@ -7,6 +7,7 @@ dist_noinst_HEADERS += \
        include/carefulputc.h \
        include/cctype.h \
        include/c.h \
+       include/caputils.h \
        include/closestream.h \
        include/colors.h \
        include/color-names.h \
diff --git a/include/caputils.h b/include/caputils.h
new file mode 100644 (file)
index 0000000..852903a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CAPUTILS_H
+#define CAPUTILS_H
+
+#include <linux/capability.h>
+
+#ifndef PR_CAP_AMBIENT
+# define PR_CAP_AMBIENT                47
+#  define PR_CAP_AMBIENT_IS_SET        1
+#  define PR_CAP_AMBIENT_RAISE 2
+#  define PR_CAP_AMBIENT_LOWER 3
+#endif
+
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, const cap_user_data_t data);
+
+extern int cap_last_cap(void);
+
+#endif /* CAPUTILS_H */
index 862a06c17c4b50332916242315129370be1f64e0..3d989abb14d2838f88e32caa295967e7dda4abcc 100644 (file)
@@ -31,6 +31,7 @@ libcommon_la_SOURCES = \
 
 if LINUX
 libcommon_la_SOURCES += \
+       lib/caputils.c \
        lib/linux_version.c \
        lib/loopdev.c
 endif
diff --git a/lib/caputils.c b/lib/caputils.c
new file mode 100644 (file)
index 0000000..17e9c01
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+
+#include "caputils.h"
+#include "pathnames.h"
+
+int cap_last_cap(void)
+{
+       /* CAP_LAST_CAP is untrustworthy. */
+       static int ret = -1;
+       int matched;
+       FILE *f;
+
+       if (ret != -1)
+               return ret;
+
+       f = fopen(_PATH_PROC_CAPLASTCAP, "r");
+       if (!f) {
+               ret = CAP_LAST_CAP;     /* guess */
+               return ret;
+       }
+
+       matched = fscanf(f, "%d", &ret);
+       fclose(f);
+
+       if (matched != 1)
+               ret = CAP_LAST_CAP;     /* guess */
+
+       return ret;
+}
index e612f7269d8acc33b8bde855b66f585f69266f7e..f8a03640bef2d74a249a25483ce6aa08aae67396 100644 (file)
@@ -32,6 +32,7 @@
 #include <unistd.h>
 
 #include "c.h"
+#include "caputils.h"
 #include "closestream.h"
 #include "nls.h"
 #include "optutils.h"
 # define PR_GET_NO_NEW_PRIVS 39
 #endif
 
-#ifndef PR_CAP_AMBIENT
-# define PR_CAP_AMBIENT                47
-#  define PR_CAP_AMBIENT_IS_SET        1
-#  define PR_CAP_AMBIENT_RAISE 2
-#  define PR_CAP_AMBIENT_LOWER 3
-#endif
-
 #define SETPRIV_EXIT_PRIVERR 127       /* how we exit when we fail to set privs */
 
 /* The shell to set SHELL env.variable if none is given in the user's passwd entry.  */
@@ -161,31 +155,6 @@ static void __attribute__((__noreturn__)) usage(void)
        exit(EXIT_SUCCESS);
 }
 
-static int real_cap_last_cap(void)
-{
-       /* CAP_LAST_CAP is untrustworthy. */
-       static int ret = -1;
-       int matched;
-       FILE *f;
-
-       if (ret != -1)
-               return ret;
-
-       f = fopen(_PATH_PROC_CAPLASTCAP, "r");
-       if (!f) {
-               ret = CAP_LAST_CAP;     /* guess */
-               return ret;
-       }
-
-       matched = fscanf(f, "%d", &ret);
-       fclose(f);
-
-       if (matched != 1)
-               ret = CAP_LAST_CAP;     /* guess */
-
-       return ret;
-}
-
 static int has_cap(enum cap_type which, unsigned int i)
 {
        switch (which) {
@@ -206,7 +175,7 @@ static int has_cap(enum cap_type which, unsigned int i)
 /* Returns the number of capabilities printed. */
 static int print_caps(FILE *f, enum cap_type which)
 {
-       int i, n = 0, max = real_cap_last_cap();
+       int i, n = 0, max = cap_last_cap();
 
        for (i = 0; i <= max; i++) {
                int ret = has_cap(which, i);
@@ -436,7 +405,7 @@ static void dump(int dumplevel)
 
 static void list_known_caps(void)
 {
-       int i, max = real_cap_last_cap();
+       int i, max = cap_last_cap();
 
        for (i = 0; i <= max; i++) {
                const char *name = capng_capability_to_name(i);
@@ -565,7 +534,7 @@ static void do_caps(enum cap_type type, const char *caps)
                        int i;
                        /* It would be really bad if -all didn't drop all
                         * caps.  It's better to just fail. */
-                       if (real_cap_last_cap() > CAP_LAST_CAP)
+                       if (cap_last_cap() > CAP_LAST_CAP)
                                errx(SETPRIV_EXIT_PRIVERR,
                                     _("libcap-ng is too old for \"all\" caps"));
                        for (i = 0; i <= CAP_LAST_CAP; i++)
@@ -575,7 +544,7 @@ static void do_caps(enum cap_type type, const char *caps)
                        if (0 <= cap)
                                cap_update(action, type, cap);
                        else if (sscanf(c + 1, "cap_%d", &cap) == 1
-                           && 0 <= cap && cap <= real_cap_last_cap())
+                           && 0 <= cap && cap <= cap_last_cap())
                                cap_update(action, type, cap);
                        else
                                errx(EXIT_FAILURE,
index db8915fe9ec4b9d89375e37c90ea94a761fdc555..4f1239d7ba5114569e7352cdd3829c796ad228b2 100644 (file)
@@ -138,6 +138,10 @@ by bind mount.
 Fork the specified \fIprogram\fR as a child process of \fBunshare\fR rather than
 running it directly.  This is useful when creating a new PID namespace.
 .TP
+.BR \-\-keep\-caps
+When the \fB--user\fP option is given, ensure that capabilities granted
+in the user namespace are preserved in the child process.
+.TP
 .BR \-\-kill\-child [ =\fIsigname ]
 When \fBunshare\fR terminates, have \fIsigname\fP be sent to the forked child process.
 Combined with \fB--pid\fR this allows for an easy and reliable killing of the entire
index f32e4294c5ec90025d615874d11625748004fd59..009ad7f184dd8c44d1bb43ebc32472a33c52d6ac 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "nls.h"
 #include "c.h"
+#include "caputils.h"
 #include "closestream.h"
 #include "namespace.h"
 #include "exec_shell.h"
@@ -69,7 +70,6 @@ static struct namespace_file {
 
 static int npersists;  /* number of persistent namespaces */
 
-
 enum {
        SETGROUPS_NONE = -1,
        SETGROUPS_DENY = 0,
@@ -278,6 +278,7 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" --propagation slave|shared|private|unchanged\n"
                "                           modify mount propagation in mount namespace\n"), out);
        fputs(_(" --setgroups allow|deny    control the setgroups syscall in user namespaces\n"), out);
+       fputs(_(" --keep-caps               retain capabilities granted in user namespaces\n"), out);
        fputs(USAGE_SEPARATOR, out);
        fputs(_(" -R, --root=<dir>          run the command with root directory set to <dir>\n"), out);
        fputs(_(" -w, --wd=<dir>            change working directory to <dir>\n"), out);
@@ -298,6 +299,7 @@ int main(int argc, char *argv[])
                OPT_PROPAGATION,
                OPT_SETGROUPS,
                OPT_KILLCHILD,
+               OPT_KEEPCAPS,
        };
        static const struct option longopts[] = {
                { "help",          no_argument,       NULL, 'h'             },
@@ -318,6 +320,7 @@ int main(int argc, char *argv[])
                { "map-current-user", no_argument,    NULL, 'c'             },
                { "propagation",   required_argument, NULL, OPT_PROPAGATION },
                { "setgroups",     required_argument, NULL, OPT_SETGROUPS   },
+               { "keep-caps",     no_argument,       NULL, OPT_KEEPCAPS    },
                { "setuid",        required_argument, NULL, 'S'             },
                { "setgid",        required_argument, NULL, 'G'             },
                { "root",          required_argument, NULL, 'R'             },
@@ -339,6 +342,7 @@ int main(int argc, char *argv[])
        int force_uid = 0, force_gid = 0;
        uid_t uid = 0, real_euid = geteuid();
        gid_t gid = 0, real_egid = getegid();
+       int keepcaps = 0;
 
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
@@ -421,6 +425,10 @@ int main(int argc, char *argv[])
                                kill_child_signo = SIGKILL;
                        }
                        break;
+                case OPT_KEEPCAPS:
+                       keepcaps = 1;
+                       cap_last_cap(); /* Force last cap to be cached before we fork. */
+                       break;
                case 'S':
                        uid = strtoul_or_err(optarg, _("failed to parse uid"));
                        force_uid = 1;
@@ -556,6 +564,47 @@ int main(int argc, char *argv[])
        if (force_uid && setuid(uid) < 0)       /* change UID */
                err(EXIT_FAILURE, _("setuid failed"));
 
+       /* We use capabilities system calls to propagate the permitted
+        * capabilities into the ambient set because we have already
+        * forked so are in async-signal-safe context. */
+       if (keepcaps && (unshare_flags & CLONE_NEWUSER)) {
+               struct __user_cap_header_struct header = {
+                       .version = _LINUX_CAPABILITY_VERSION_3,
+                       .pid = 0,
+               };
+
+               struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] = { 0 };
+
+               if (capget(&header, payload) < 0) {
+                       err(EXIT_FAILURE, _("capget failed"));
+               }
+
+               /* In order the make capabilities ambient, we first need to ensure
+                * that they are all inheritable. */
+               payload[0].inheritable = payload[0].permitted;
+               payload[1].inheritable = payload[1].permitted;
+
+               if (capset(&header, payload) < 0) {
+                       err(EXIT_FAILURE, _("capset failed"));
+               }
+
+               uint64_t effective = ((uint64_t)payload[1].effective << 32) |  (uint64_t)payload[0].effective;
+
+               for (int cap = 0; cap < 64; cap++) {
+                       /* This is the same check as cap_valid(), but using
+                        * the runtime value for the last valid cap. */
+                       if (cap < 0 || cap > cap_last_cap()) {
+                               continue;
+                       }
+
+                       if (effective & (1 << cap)) {
+                               if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0) {
+                                       err(EXIT_FAILURE, _("prctl(PR_CAP_AMBIENT) failed"));
+                               }
+                       }
+                }
+        }
+
        if (optind < argc) {
                execvp(argv[optind], argv + optind);
                errexec(argv[optind]);