]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/namespace.c
namespace: be more careful when handling namespacing failures gracefully
[thirdparty/systemd.git] / src / core / namespace.c
index cb34a79859c5010582716e186bab5ecc41c218eb..62518e1c4cc569891b7ef48d6a830c8346885c55 100644 (file)
@@ -1,9 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-***/
 
 #include <errno.h>
 #include <sched.h>
@@ -72,7 +67,7 @@ typedef struct MountEntry {
 } MountEntry;
 
 /* If MountAPIVFS= is used, let's mount /sys and /proc into the it, but only as a fallback if the user hasn't mounted
- * something there already. These mounts are hence overriden by any other explicitly configured mounts. */
+ * something there already. These mounts are hence overridden by any other explicitly configured mounts. */
 static const MountEntry apivfs_table[] = {
         { "/proc",               PROCFS,       false },
         { "/dev",                BIND_DEV,     false },
@@ -81,23 +76,26 @@ static const MountEntry apivfs_table[] = {
 
 /* ProtectKernelTunables= option and the related filesystem APIs */
 static const MountEntry protect_kernel_tunables_table[] = {
-        { "/proc/sys",           READONLY,     false },
-        { "/proc/sysrq-trigger", READONLY,     true  },
-        { "/proc/latency_stats", READONLY,     true  },
-        { "/proc/mtrr",          READONLY,     true  },
-        { "/proc/apm",           READONLY,     true  }, /* Obsolete API, there's no point in permitting access to this, ever */
         { "/proc/acpi",          READONLY,     true  },
-        { "/proc/timer_stats",   READONLY,     true  },
+        { "/proc/apm",           READONLY,     true  }, /* Obsolete API, there's no point in permitting access to this, ever */
         { "/proc/asound",        READONLY,     true  },
         { "/proc/bus",           READONLY,     true  },
         { "/proc/fs",            READONLY,     true  },
         { "/proc/irq",           READONLY,     true  },
+        { "/proc/kallsyms",      INACCESSIBLE, true  },
+        { "/proc/kcore",         INACCESSIBLE, true  },
+        { "/proc/latency_stats", READONLY,     true  },
+        { "/proc/mtrr",          READONLY,     true  },
+        { "/proc/scsi",          READONLY,     true  },
+        { "/proc/sys",           READONLY,     false },
+        { "/proc/sysrq-trigger", READONLY,     true  },
+        { "/proc/timer_stats",   READONLY,     true  },
         { "/sys",                READONLY,     false },
-        { "/sys/kernel/debug",   READONLY,     true  },
-        { "/sys/kernel/tracing", READONLY,     true  },
         { "/sys/fs/bpf",         READONLY,     true  },
         { "/sys/fs/cgroup",      READWRITE,    false }, /* READONLY is set by ProtectControlGroups= option */
         { "/sys/fs/selinux",     READWRITE,    true  },
+        { "/sys/kernel/debug",   READONLY,     true  },
+        { "/sys/kernel/tracing", READONLY,     true  },
 };
 
 /* ProtectKernelModules= option */
@@ -237,8 +235,10 @@ static int append_access_mounts(MountEntry **p, char **strv, MountMode mode, boo
                         needs_prefix = true;
                 }
 
-                if (!path_is_absolute(e))
+                if (!path_is_absolute(e)) {
+                        log_debug("Path is not absolute: %s", e);
                         return -EINVAL;
+                }
 
                 *((*p)++) = (MountEntry) {
                         .path_const = e,
@@ -275,8 +275,8 @@ static int append_empty_dir_mounts(MountEntry **p, char **strv) {
         return 0;
 }
 
-static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n) {
-        unsigned i;
+static int append_bind_mounts(MountEntry **p, const BindMount *binds, size_t n) {
+        size_t i;
 
         assert(p);
 
@@ -295,8 +295,8 @@ static int append_bind_mounts(MountEntry **p, const BindMount *binds, unsigned n
         return 0;
 }
 
-static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, unsigned n) {
-        unsigned i;
+static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, size_t n) {
+        size_t i;
         int r;
 
         assert(p);
@@ -307,8 +307,10 @@ static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs,
                 unsigned long flags = MS_NODEV|MS_STRICTATIME;
                 bool ro = false;
 
-                if (!path_is_absolute(t->path))
+                if (!path_is_absolute(t->path)) {
+                        log_debug("Path is not absolute: %s", t->path);
                         return -EINVAL;
+                }
 
                 if (!isempty(t->options)) {
                         str = strjoin("mode=0755,", t->options);
@@ -317,9 +319,9 @@ static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs,
 
                         r = mount_option_mangle(str, MS_NODEV|MS_STRICTATIME, &flags, &o);
                         if (r < 0)
-                                return r;
+                                return log_debug_errno(r, "Failed to parse mount option '%s': %m", str);
 
-                        ro = !!(flags & MS_RDONLY);
+                        ro = flags & MS_RDONLY;
                         if (ro)
                                 flags ^= MS_RDONLY;
                 }
@@ -338,8 +340,8 @@ static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs,
         return 0;
 }
 
-static int append_static_mounts(MountEntry **p, const MountEntry *mounts, unsigned n, bool ignore_protect) {
-        unsigned i;
+static int append_static_mounts(MountEntry **p, const MountEntry *mounts, size_t n, bool ignore_protect) {
+        size_t i;
 
         assert(p);
         assert(mounts);
@@ -418,8 +420,8 @@ static int mount_path_compare(const void *a, const void *b) {
         return 0;
 }
 
-static int prefix_where_needed(MountEntry *m, unsigned n, const char *root_directory) {
-        unsigned i;
+static int prefix_where_needed(MountEntry *m, size_t n, const char *root_directory) {
+        size_t i;
 
         /* Prefixes all paths in the bind mount table with the root directory if it is specified and the entry needs
          * that. */
@@ -444,7 +446,7 @@ static int prefix_where_needed(MountEntry *m, unsigned n, const char *root_direc
         return 0;
 }
 
-static void drop_duplicates(MountEntry *m, unsigned *n) {
+static void drop_duplicates(MountEntry *m, size_t *n) {
         MountEntry *f, *t, *previous;
 
         assert(m);
@@ -473,7 +475,7 @@ static void drop_duplicates(MountEntry *m, unsigned *n) {
         *n = t - m;
 }
 
-static void drop_inaccessible(MountEntry *m, unsigned *n) {
+static void drop_inaccessible(MountEntry *m, size_t *n) {
         MountEntry *f, *t;
         const char *clear = NULL;
 
@@ -502,7 +504,7 @@ static void drop_inaccessible(MountEntry *m, unsigned *n) {
         *n = t - m;
 }
 
-static void drop_nop(MountEntry *m, unsigned *n) {
+static void drop_nop(MountEntry *m, size_t *n) {
         MountEntry *f, *t;
 
         assert(m);
@@ -541,7 +543,7 @@ static void drop_nop(MountEntry *m, unsigned *n) {
         *n = t - m;
 }
 
-static void drop_outside_root(const char *root_directory, MountEntry *m, unsigned *n) {
+static void drop_outside_root(const char *root_directory, MountEntry *m, size_t *n) {
         MountEntry *f, *t;
 
         assert(m);
@@ -1002,6 +1004,7 @@ static int apply_mount(
 }
 
 static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self_mountinfo) {
+        bool submounts = false;
         int r = 0;
 
         assert(m);
@@ -1012,8 +1015,10 @@ static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self
                         /* Make superblock readonly */
                         if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT | MS_RDONLY | m->flags, mount_entry_options(m)) < 0)
                                 r = -errno;
-                } else
+                } else {
+                        submounts = true;
                         r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), true, blacklist, proc_self_mountinfo);
+                }
         } else if (m->mode == PRIVATE_DEV) {
                 /* Superblock can be readonly but the submounts can't */
                 if (mount(NULL, mount_entry_path(m), NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0)
@@ -1028,41 +1033,42 @@ static int make_read_only(const MountEntry *m, char **blacklist, FILE *proc_self
         if (r == -ENOENT && m->ignore)
                 r = 0;
 
-        return r;
+        if (r < 0)
+                return log_debug_errno(r, "Failed to re-mount '%s'%s read-only: %m", mount_entry_path(m),
+                                       submounts ? " and its submounts" : "");
+
+        return 0;
 }
 
-static bool namespace_info_mount_apivfs(const char *root_directory, const NamespaceInfo *ns_info) {
+static bool namespace_info_mount_apivfs(const NamespaceInfo *ns_info) {
         assert(ns_info);
 
         /*
          * ProtectControlGroups= and ProtectKernelTunables= imply MountAPIVFS=,
          * since to protect the API VFS mounts, they need to be around in the
-         * first place... and RootDirectory= or RootImage= need to be set.
+         * first place...
          */
 
-        /* root_directory should point to a mount point */
-        return root_directory &&
-                (ns_info->mount_apivfs ||
-                 ns_info->protect_control_groups ||
-                 ns_info->protect_kernel_tunables);
+        return ns_info->mount_apivfs ||
+                ns_info->protect_control_groups ||
+                ns_info->protect_kernel_tunables;
 }
 
-static unsigned namespace_calculate_mounts(
-                const char* root_directory,
+static size_t namespace_calculate_mounts(
                 const NamespaceInfo *ns_info,
                 char** read_write_paths,
                 char** read_only_paths,
                 char** inaccessible_paths,
                 char** empty_directories,
-                unsigned n_bind_mounts,
-                unsigned n_temporary_filesystems,
+                size_t n_bind_mounts,
+                size_t n_temporary_filesystems,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 ProtectHome protect_home,
                 ProtectSystem protect_system) {
 
-        unsigned protect_home_cnt;
-        unsigned protect_system_cnt =
+        size_t protect_home_cnt;
+        size_t protect_system_cnt =
                 (protect_system == PROTECT_SYSTEM_STRICT ?
                  ELEMENTSOF(protect_system_strict_table) :
                  ((protect_system == PROTECT_SYSTEM_FULL) ?
@@ -1090,10 +1096,11 @@ static unsigned namespace_calculate_mounts(
                 (ns_info->protect_control_groups ? 1 : 0) +
                 (ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
                 protect_home_cnt + protect_system_cnt +
-                (namespace_info_mount_apivfs(root_directory, ns_info) ? ELEMENTSOF(apivfs_table) : 0);
+                (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0);
 }
 
-static void normalize_mounts(const char *root_directory, MountEntry *mounts, unsigned *n_mounts) {
+static void normalize_mounts(const char *root_directory, MountEntry *mounts, size_t *n_mounts) {
+        assert(root_directory);
         assert(n_mounts);
         assert(mounts || *n_mounts == 0);
 
@@ -1114,9 +1121,9 @@ int setup_namespace(
                 char** inaccessible_paths,
                 char** empty_directories,
                 const BindMount *bind_mounts,
-                unsigned n_bind_mounts,
+                size_t n_bind_mounts,
                 const TemporaryFileSystem *temporary_filesystems,
-                unsigned n_temporary_filesystems,
+                size_t n_temporary_filesystems,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 ProtectHome protect_home,
@@ -1129,11 +1136,9 @@ int setup_namespace(
         _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
         _cleanup_free_ void *root_hash = NULL;
         MountEntry *m, *mounts = NULL;
-        size_t root_hash_size = 0;
-        bool make_slave = false;
-        const char *root;
-        unsigned n_mounts;
+        size_t n_mounts, root_hash_size = 0;
         bool require_prefix = false;
+        const char *root;
         int r = 0;
 
         assert(ns_info);
@@ -1153,39 +1158,36 @@ int setup_namespace(
                                              dissect_image_flags & DISSECT_IMAGE_READ_ONLY ? O_RDONLY : O_RDWR,
                                              &loop_device);
                 if (r < 0)
-                        return r;
+                        return log_debug_errno(r, "Failed to create loop device for root image: %m");
 
                 r = root_hash_load(root_image, &root_hash, &root_hash_size);
                 if (r < 0)
-                        return r;
+                        return log_debug_errno(r, "Failed to load root hash: %m");
 
                 r = dissect_image(loop_device->fd, root_hash, root_hash_size, dissect_image_flags, &dissected_image);
                 if (r < 0)
-                        return r;
+                        return log_debug_errno(r, "Failed to dissect image: %m");
 
                 r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, dissect_image_flags, &decrypted_image);
                 if (r < 0)
-                        return r;
+                        return log_debug_errno(r, "Failed to decrypt dissected image: %m");
         }
 
         if (root_directory)
                 root = root_directory;
-        else if (root_image || n_bind_mounts > 0 || n_temporary_filesystems > 0) {
-
-                /* If we are booting from an image, create a mount point for the image, if it's still missing. We use
-                 * the same mount point for all images, which is safe, since they all live in their own namespaces
-                 * after all, and hence won't see each other. We also use such a root directory whenever there are bind
-                 * mounts configured, so that their source mounts are never obstructed by mounts we already applied
-                 * while we are applying them. */
+        else {
+                /* Always create the mount namespace in a temporary directory, instead of operating
+                 * directly in the root. The temporary directory prevents any mounts from being
+                 * potentially obscured my other mounts we already applied.
+                 * We use the same mount point for all images, which is safe, since they all live
+                 * in their own namespaces after all, and hence won't see each other. */
 
                 root = "/run/systemd/unit-root";
                 (void) mkdir_label(root, 0700);
                 require_prefix = true;
-        } else
-                root = NULL;
+        }
 
         n_mounts = namespace_calculate_mounts(
-                        root,
                         ns_info,
                         read_write_paths,
                         read_only_paths,
@@ -1196,10 +1198,6 @@ int setup_namespace(
                         tmp_dir, var_tmp_dir,
                         protect_home, protect_system);
 
-        /* Set mount slave mode */
-        if (root || n_mounts > 0)
-                make_slave = true;
-
         if (n_mounts > 0) {
                 m = mounts = (MountEntry *) alloca0(n_mounts * sizeof(MountEntry));
                 r = append_access_mounts(&m, read_write_paths, READWRITE, require_prefix);
@@ -1276,7 +1274,7 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
-                if (namespace_info_mount_apivfs(root, ns_info)) {
+                if (namespace_info_mount_apivfs(ns_info)) {
                         r = append_static_mounts(&m, apivfs_table, ELEMENTSOF(apivfs_table), ns_info->ignore_protect_paths);
                         if (r < 0)
                                 goto finish;
@@ -1289,33 +1287,44 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
-                normalize_mounts(root_directory, mounts, &n_mounts);
+                normalize_mounts(root, mounts, &n_mounts);
         }
 
+        /* All above is just preparation, figuring out what to do. Let's now actually start doing something. */
+
         if (unshare(CLONE_NEWNS) < 0) {
-                r = -errno;
+                r = log_debug_errno(errno, "Failed to unshare the mount namespace: %m");
+                if (IN_SET(r, -EACCES, -EPERM, -EOPNOTSUPP, -ENOSYS))
+                        /* If the kernel doesn't support namespaces, or when there's a MAC or seccomp filter in place
+                         * that doesn't allow us to create namespaces (or a missing cap), then propagate a recognizable
+                         * error back, which the caller can use to detect this case (and only this) and optionally
+                         * continue without namespacing applied. */
+                        r = -ENOANO;
+
                 goto finish;
         }
 
-        if (make_slave) {
-                /* Remount / as SLAVE so that nothing now mounted in the namespace
-                   shows up in the parent */
-                if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
-                        r = -errno;
-                        goto finish;
-                }
+        /* Remount / as SLAVE so that nothing now mounted in the namespace
+         * shows up in the parent */
+        if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
+                r = log_debug_errno(errno, "Failed to remount '/' as SLAVE: %m");
+                goto finish;
         }
 
         if (root_image) {
                 /* A root image is specified, mount it to the right place */
                 r = dissected_image_mount(dissected_image, root, UID_INVALID, dissect_image_flags);
-                if (r < 0)
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to mount root image: %m");
                         goto finish;
+                }
 
                 if (decrypted_image) {
                         r = decrypted_image_relinquish(decrypted_image);
-                        if (r < 0)
+                        if (r < 0) {
+                                log_debug_errno(r, "Failed to relinquish decrypted image: %m");
                                 goto finish;
+                        }
                 }
 
                 loop_device_relinquish(loop_device);
@@ -1324,20 +1333,22 @@ int setup_namespace(
 
                 /* A root directory is specified. Turn its directory into bind mount, if it isn't one yet. */
                 r = path_is_mount_point(root, NULL, AT_SYMLINK_FOLLOW);
-                if (r < 0)
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to detect that %s is a mount point or not: %m", root);
                         goto finish;
+                }
                 if (r == 0) {
                         if (mount(root, root, NULL, MS_BIND|MS_REC, NULL) < 0) {
-                                r = -errno;
+                                r = log_debug_errno(errno, "Failed to bind mount '%s': %m", root);
                                 goto finish;
                         }
                 }
 
-        } else if (root) {
+        } else {
 
                 /* Let's mount the main root directory to the root directory to use */
                 if (mount("/", root, NULL, MS_BIND|MS_REC, NULL) < 0) {
-                        r = -errno;
+                        r = log_debug_errno(errno, "Failed to bind mount '/' on '%s': %m", root);
                         goto finish;
                 }
         }
@@ -1349,13 +1360,13 @@ int setup_namespace(
         if (n_mounts > 0) {
                 _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
                 char **blacklist;
-                unsigned j;
+                size_t j;
 
                 /* Open /proc/self/mountinfo now as it may become unavailable if we mount anything on top of /proc.
                  * For example, this is the case with the option: 'InaccessiblePaths=/proc' */
                 proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
                 if (!proc_self_mountinfo) {
-                        r = -errno;
+                        r = log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m");
                         goto finish;
                 }
 
@@ -1390,7 +1401,7 @@ int setup_namespace(
                         if (!again)
                                 break;
 
-                        normalize_mounts(root_directory, mounts, &n_mounts);
+                        normalize_mounts(root, mounts, &n_mounts);
                 }
 
                 /* Create a blacklist we can pass to bind_mount_recursive() */
@@ -1407,18 +1418,18 @@ int setup_namespace(
                 }
         }
 
-        if (root) {
-                /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
-                r = mount_move_root(root);
-                if (r < 0)
-                        goto finish;
+        /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
+        r = mount_move_root(root);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to mount root with MS_MOVE: %m");
+                goto finish;
         }
 
         /* Remount / as the desired mode. Note that this will not
          * reestablish propagation from our side to the host, since
          * what's disconnected is disconnected. */
         if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) {
-                r = -errno;
+                r = log_debug_errno(errno, "Failed to remount '/' with desired mount flags: %m");
                 goto finish;
         }
 
@@ -1431,8 +1442,8 @@ finish:
         return r;
 }
 
-void bind_mount_free_many(BindMount *b, unsigned n) {
-        unsigned i;
+void bind_mount_free_many(BindMount *b, size_t n) {
+        size_t i;
 
         assert(b || n == 0);
 
@@ -1444,7 +1455,7 @@ void bind_mount_free_many(BindMount *b, unsigned n) {
         free(b);
 }
 
-int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
+int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) {
         _cleanup_free_ char *s = NULL, *d = NULL;
         BindMount *c;
 
@@ -1477,8 +1488,8 @@ int bind_mount_add(BindMount **b, unsigned *n, const BindMount *item) {
         return 0;
 }
 
-void temporary_filesystem_free_many(TemporaryFileSystem *t, unsigned n) {
-        unsigned i;
+void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n) {
+        size_t i;
 
         assert(t || n == 0);
 
@@ -1492,7 +1503,7 @@ void temporary_filesystem_free_many(TemporaryFileSystem *t, unsigned n) {
 
 int temporary_filesystem_add(
                 TemporaryFileSystem **t,
-                unsigned *n,
+                size_t *n,
                 const char *path,
                 const char *options) {
 
@@ -1677,19 +1688,7 @@ static const char *const protect_home_table[_PROTECT_HOME_MAX] = {
         [PROTECT_HOME_TMPFS] = "tmpfs",
 };
 
-DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome);
-
-ProtectHome parse_protect_home_or_bool(const char *s) {
-        int r;
-
-        r = parse_boolean(s);
-        if (r > 0)
-                return PROTECT_HOME_YES;
-        if (r == 0)
-                return PROTECT_HOME_NO;
-
-        return protect_home_from_string(s);
-}
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_home, ProtectHome, PROTECT_HOME_YES);
 
 static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = {
         [PROTECT_SYSTEM_NO] = "no",
@@ -1698,19 +1697,7 @@ static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = {
         [PROTECT_SYSTEM_STRICT] = "strict",
 };
 
-DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem);
-
-ProtectSystem parse_protect_system_or_bool(const char *s) {
-        int r;
-
-        r = parse_boolean(s);
-        if (r > 0)
-                return PROTECT_SYSTEM_YES;
-        if (r == 0)
-                return PROTECT_SYSTEM_NO;
-
-        return protect_system_from_string(s);
-}
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_system, ProtectSystem, PROTECT_SYSTEM_YES);
 
 static const char* const namespace_type_table[] = {
         [NAMESPACE_MOUNT] = "mnt",