]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
detect-virt: add --private-users switch to check if a userns is active
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 21 Oct 2016 03:41:21 +0000 (23:41 -0400)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 27 Oct 2016 00:12:51 +0000 (20:12 -0400)
Various things don't work when we're running in a user namespace, but it's
pretty hard to reliably detect if that is true.

A function is added which looks at /proc/self/uid_map and returns false
if the default "0 0 UINT32_MAX" is found, and true if it finds anything else.
This misses the case where an 1:1 mapping with the full range was used, but
I don't know how to distinguish this case.

'systemd-detect-virt --private-users' is very similar to
'systemd-detect-virt --chroot', but we check for a user namespace instead.

man/systemd-detect-virt.xml
man/systemd.unit.xml
src/basic/virt.c
src/basic/virt.h
src/detect-virt/detect-virt.c

index 61a5f8937f8e31e1fc8204d5b3193714dcb62530..996c2fa256e099277a1088267d61ecefd1ad7783 100644 (file)
@@ -50,7 +50,8 @@
 
   <refsynopsisdiv>
     <cmdsynopsis>
-      <command>systemd-detect-virt <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+      <command>systemd-detect-virt</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
         environment or not.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--private-users</option></term>
+
+        <listitem><para>Detect whether invoked in a user namespace.  In this mode, no
+        output is written, but the return value indicates whether the process was invoked
+        inside of a user namespace or not. See
+        <citerefentry project='man-pages'><refentrytitle>user_namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+        for more information.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>-q</option></term>
         <term><option>--quiet</option></term>
     <para>
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
-      <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+      <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>namespaces</refentrytitle><manvolnum>7</manvolnum></citerefentry>
     </para>
   </refsect1>
 
index 04efee28916ab0e5027447386b305b259d1ab224..40c4cfd854bddf31d1f01154e875c0a8f578b7d0 100644 (file)
         <varname>systemd-nspawn</varname>,
         <varname>docker</varname>,
         <varname>rkt</varname> to test
-        against a specific implementation. See
+        against a specific implementation, or
+        <varname>private-users</varname> to check whether we are running in a user namespace. See
         <citerefentry><refentrytitle>systemd-detect-virt</refentrytitle><manvolnum>1</manvolnum></citerefentry>
         for a full list of known virtualization technologies and their
         identifiers. If multiple virtualization technologies are
index 41012d52a00ca983a077c8add32af1317b34f34d..69b0f96183afeaa7a3a54206fec505eb269ba4f4 100644 (file)
@@ -485,6 +485,76 @@ int detect_virtualization(void) {
         return r;
 }
 
+static int userns_has_mapping(const char *name) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *buf = NULL;
+        size_t n_allocated = 0;
+        ssize_t n;
+        uint32_t a, b, c;
+        int r;
+
+        f = fopen(name, "re");
+        if (!f) {
+                log_debug_errno(errno, "Failed to open %s: %m", name);
+                return errno == -ENOENT ? false : -errno;
+        }
+
+        n = getline(&buf, &n_allocated, f);
+        if (n < 0) {
+                if (feof(f)) {
+                        log_debug("%s is empty, we're in an uninitialized user namespace", name);
+                        return true;
+                }
+
+                return log_debug_errno(errno, "Failed to read %s: %m", name);
+        }
+
+        r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
+        if (r < 3)
+                return log_debug_errno(errno, "Failed to parse %s: %m", name);
+
+        if (a == 0 && b == 0 && c == UINT32_MAX) {
+                /* The kernel calls mappings_overlap() and does not allow overlaps */
+                log_debug("%s has a full 1:1 mapping", name);
+                return false;
+        }
+
+        /* Anything else implies that we are in a user namespace */
+        log_debug("Mapping found in %s, we're in a user namespace", name);
+        return true;
+}
+
+int running_in_userns(void) {
+        _cleanup_free_ char *line = NULL;
+        int r;
+
+        r = userns_has_mapping("/proc/self/uid_map");
+        if (r != 0)
+                return r;
+
+        r = userns_has_mapping("/proc/self/gid_map");
+        if (r != 0)
+                return r;
+
+        /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
+         * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
+         * also does not exist. We cannot distinguish those two cases, so assume that
+         * we're running on a stripped-down recent kernel, rather than on an old one,
+         * and if the file is not found, return false.
+         */
+        r = read_one_line_file("/proc/self/setgroups", &line);
+        if (r < 0) {
+                log_debug_errno(r, "/proc/self/setgroups: %m");
+                return r == -ENOENT ? false : r;
+        }
+
+        truncate_nl(line);
+        r = streq(line, "deny");
+        /* See user_namespaces(7) for a description of this "setgroups" contents. */
+        log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
+        return r;
+}
+
 int running_in_chroot(void) {
         int ret;
 
index bc5b3ae94dce7a7bbb7e3749744c80f6183e9a86..7d151691121744817c223e410f53c4f3a13af847 100644 (file)
@@ -67,6 +67,7 @@ int detect_vm(void);
 int detect_container(void);
 int detect_virtualization(void);
 
+int running_in_userns(void);
 int running_in_chroot(void);
 
 const char *virtualization_to_string(int v) _const_;
index 5d51589a3171d458596e36a9f58ba7a79a3afe8e..4b8956f0ade6a6fa7b4719df3c433bb6e0eabdb9 100644 (file)
@@ -31,6 +31,7 @@ static enum {
         ONLY_VM,
         ONLY_CONTAINER,
         ONLY_CHROOT,
+        ONLY_PRIVATE_USERS,
 } arg_mode = ANY_VIRTUALIZATION;
 
 static void help(void) {
@@ -41,6 +42,7 @@ static void help(void) {
                "  -c --container        Only detect whether we are run in a container\n"
                "  -v --vm               Only detect whether we are run in a VM\n"
                "  -r --chroot           Detect whether we are run in a chroot() environment\n"
+               "     --private-users    Only detect whether we are running in a user namespace\n"
                "  -q --quiet            Don't output anything, just set return value\n"
                , program_invocation_short_name);
 }
@@ -48,16 +50,18 @@ static void help(void) {
 static int parse_argv(int argc, char *argv[]) {
 
         enum {
-                ARG_VERSION = 0x100
+                ARG_VERSION = 0x100,
+                ARG_PRIVATE_USERS,
         };
 
         static const struct option options[] = {
-                { "help",      no_argument,       NULL, 'h'           },
-                { "version",   no_argument,       NULL, ARG_VERSION   },
-                { "container", no_argument,       NULL, 'c'           },
-                { "vm",        no_argument,       NULL, 'v'           },
-                { "chroot",    no_argument,       NULL, 'r'           },
-                { "quiet",     no_argument,       NULL, 'q'           },
+                { "help",          no_argument, NULL, 'h'               },
+                { "version",       no_argument, NULL, ARG_VERSION       },
+                { "container",     no_argument, NULL, 'c'               },
+                { "vm",            no_argument, NULL, 'v'               },
+                { "chroot",        no_argument, NULL, 'r'               },
+                { "private-users", no_argument, NULL, ARG_PRIVATE_USERS },
+                { "quiet",         no_argument, NULL, 'q'               },
                 {}
         };
 
@@ -85,6 +89,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mode = ONLY_CONTAINER;
                         break;
 
+                case ARG_PRIVATE_USERS:
+                        arg_mode = ONLY_PRIVATE_USERS;
+                        break;
+
                 case 'v':
                         arg_mode = ONLY_VM;
                         break;
@@ -151,6 +159,15 @@ int main(int argc, char *argv[]) {
 
                 return r ? EXIT_SUCCESS : EXIT_FAILURE;
 
+        case ONLY_PRIVATE_USERS:
+                r = running_in_userns();
+                if (r < 0) {
+                        log_error_errno(r, "Failed to check for user namespace: %m");
+                        return EXIT_FAILURE;
+                }
+
+                return r ? EXIT_SUCCESS : EXIT_FAILURE;
+
         case ANY_VIRTUALIZATION:
         default:
                 r = detect_virtualization();