]> 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 32d8ca63efbd029c017b9a8351d9a7267ba8441a..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>
@@ -61,16 +56,18 @@ typedef struct MountEntry {
         bool ignore:1;            /* Ignore if path does not exist? */
         bool has_prefix:1;        /* Already is prefixed by the root dir? */
         bool read_only:1;         /* Shall this mount point be read-only? */
+        bool applied:1;           /* Already applied */
         char *path_malloc;        /* Use this instead of 'path_const' if we had to allocate memory */
         const char *source_const; /* The source path, for bind mounts */
         char *source_malloc;
         const char *options_const;/* Mount options for tmpfs */
         char *options_malloc;
         unsigned long flags;      /* Mount flags used by EMPTY_DIR and TMPFS. Do not include MS_RDONLY here, but please use read_only. */
+        unsigned n_followed;
 } 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 },
@@ -79,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 */
@@ -235,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,
@@ -273,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);
 
@@ -293,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);
@@ -305,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);
@@ -315,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;
                 }
@@ -336,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);
@@ -410,15 +414,14 @@ static int mount_path_compare(const void *a, const void *b) {
         /* If the paths are equal, check the mode */
         if (p->mode < q->mode)
                 return -1;
-
         if (p->mode > q->mode)
                 return 1;
 
         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. */
@@ -443,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);
@@ -454,8 +457,10 @@ static void drop_duplicates(MountEntry *m, unsigned *n) {
         for (f = m, t = m, previous = NULL; f < m + *n; f++) {
 
                 /* The first one wins (which is the one with the more restrictive mode), see mount_path_compare()
-                 * above. */
-                if (previous && path_equal(mount_entry_path(f), mount_entry_path(previous))) {
+                 * above. Note that we only drop duplicates that haven't been mounted yet. */
+                if (previous &&
+                    path_equal(mount_entry_path(f), mount_entry_path(previous)) &&
+                    !f->applied && !previous->applied) {
                         log_debug("%s is duplicate.", mount_entry_path(f));
                         previous->read_only = previous->read_only || mount_entry_read_only(f); /* Propagate the read-only flag to the remaining entry */
                         mount_entry_done(f);
@@ -470,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;
 
@@ -499,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);
@@ -538,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);
@@ -817,36 +822,37 @@ static int mount_tmpfs(const MountEntry *m) {
         return 1;
 }
 
-static int mount_entry_chase(
+static int follow_symlink(
                 const char *root_directory,
-                const MountEntry *m,
-                const char *path,
-                bool chase_nonexistent,
-                char **location) {
+                MountEntry *m) {
 
-        char *chased;
+        _cleanup_free_ char *target = NULL;
         int r;
 
-        assert(m);
+        /* Let's chase symlinks, but only one step at a time. That's because depending where the symlink points we
+         * might need to change the order in which we mount stuff. Hence: let's normalize piecemeal, and do one step at
+         * a time by specifying CHASE_STEP. This function returns 0 if we resolved one step, and > 0 if we reached the
+         * end and already have a fully normalized name. */
 
-        /* Since mount() will always follow symlinks and we need to take the different root directory into account we
-         * chase the symlinks on our own first. This is called for the destination path, as well as the source path (if
-         * that applies). The result is stored in "location". */
+        r = chase_symlinks(mount_entry_path(m), root_directory, CHASE_STEP|CHASE_NONEXISTENT, &target);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to chase symlinks '%s': %m", mount_entry_path(m));
+        if (r > 0) /* Reached the end, nothing more to resolve */
+                return 1;
 
-        r = chase_symlinks(path, root_directory, CHASE_TRAIL_SLASH | (chase_nonexistent ? CHASE_NONEXISTENT : 0), &chased);
-        if (r == -ENOENT && m->ignore) {
-                log_debug_errno(r, "Path %s does not exist, ignoring.", path);
-                return 0;
+        if (m->n_followed >= CHASE_SYMLINKS_MAX) { /* put a boundary on things */
+                log_debug("Symlink loop on '%s'.", mount_entry_path(m));
+                return -ELOOP;
         }
-        if (r < 0)
-                return log_debug_errno(r, "Failed to follow symlinks on %s: %m", path);
 
-        log_debug("Followed symlinks %s → %s.", path, chased);
+        log_debug("Followed mount entry path symlink %s → %s.", mount_entry_path(m), target);
 
-        free(*location);
-        *location = chased;
+        free_and_replace(m->path_malloc, target);
+        m->has_prefix = true;
 
-        return 1;
+        m->n_followed ++;
+
+        return 0;
 }
 
 static int apply_mount(
@@ -859,10 +865,6 @@ static int apply_mount(
 
         assert(m);
 
-        r = mount_entry_chase(root_directory, m, mount_entry_path(m), !IN_SET(m->mode, INACCESSIBLE, READONLY, READWRITE), &m->path_malloc);
-        if (r <= 0)
-                return r;
-
         log_debug("Applying namespace mount on %s", mount_entry_path(m));
 
         switch (m->mode) {
@@ -875,8 +877,12 @@ static int apply_mount(
                  * inaccessible path. */
                 (void) umount_recursive(mount_entry_path(m), 0);
 
-                if (lstat(mount_entry_path(m), &target) < 0)
+                if (lstat(mount_entry_path(m), &target) < 0) {
+                        if (errno == ENOENT && m->ignore)
+                                return 0;
+
                         return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", mount_entry_path(m));
+                }
 
                 what = mode_to_inaccessible_node(target.st_mode);
                 if (!what) {
@@ -889,6 +895,8 @@ static int apply_mount(
         case READONLY:
         case READWRITE:
                 r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
+                if (r == -ENOENT && m->ignore)
+                        return 0;
                 if (r < 0)
                         return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", mount_entry_path(m));
                 if (r > 0) /* Nothing to do here, it is already a mount. We just later toggle the MS_RDONLY bit for the mount point if needed. */
@@ -901,16 +909,29 @@ static int apply_mount(
                 rbind = false;
 
                 _fallthrough_;
-        case BIND_MOUNT_RECURSIVE:
-                /* Also chase the source mount */
+        case BIND_MOUNT_RECURSIVE: {
+                _cleanup_free_ char *chased = NULL;
 
-                r = mount_entry_chase(root_directory, m, mount_entry_source(m), false, &m->source_malloc);
-                if (r <= 0)
-                        return r;
+                /* Since mount() will always follow symlinks we chase the symlinks on our own first. Note that bind
+                 * mount source paths are always relative to the host root, hence we pass NULL as root directory to
+                 * chase_symlinks() here. */
+
+                r = chase_symlinks(mount_entry_source(m), NULL, CHASE_TRAIL_SLASH, &chased);
+                if (r == -ENOENT && m->ignore) {
+                        log_debug_errno(r, "Path %s does not exist, ignoring.", mount_entry_source(m));
+                        return 0;
+                }
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to follow symlinks on %s: %m", mount_entry_source(m));
+
+                log_debug("Followed source symlinks %s → %s.", mount_entry_source(m), chased);
+
+                free_and_replace(m->source_malloc, chased);
 
                 what = mount_entry_source(m);
                 make = true;
                 break;
+        }
 
         case EMPTY_DIR:
         case TMPFS:
@@ -983,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);
@@ -993,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)
@@ -1009,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) ?
@@ -1071,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);
 
@@ -1095,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,
@@ -1110,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);
@@ -1134,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,
@@ -1177,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);
@@ -1257,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;
@@ -1270,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);
@@ -1305,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;
                 }
         }
@@ -1330,21 +1360,48 @@ 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;
                 }
 
-                /* First round, add in all special mounts we need */
-                for (m = mounts; m < mounts + n_mounts; ++m) {
-                        r = apply_mount(root, m);
-                        if (r < 0)
-                                goto finish;
+                /* First round, establish all mounts we need */
+                for (;;) {
+                        bool again = false;
+
+                        for (m = mounts; m < mounts + n_mounts; ++m) {
+
+                                if (m->applied)
+                                        continue;
+
+                                r = follow_symlink(root, m);
+                                if (r < 0)
+                                        goto finish;
+                                if (r == 0) {
+                                        /* We hit a symlinked mount point. The entry got rewritten and might point to a
+                                         * very different place now. Let's normalize the changed list, and start from
+                                         * the beginning. After all to mount the entry at the new location we might
+                                         * need some other mounts first */
+                                        again = true;
+                                        break;
+                                }
+
+                                r = apply_mount(root, m);
+                                if (r < 0)
+                                        goto finish;
+
+                                m->applied = true;
+                        }
+
+                        if (!again)
+                                break;
+
+                        normalize_mounts(root, mounts, &n_mounts);
                 }
 
                 /* Create a blacklist we can pass to bind_mount_recursive() */
@@ -1361,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;
         }
 
@@ -1385,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);
 
@@ -1398,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;
 
@@ -1431,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);
 
@@ -1446,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) {
 
@@ -1631,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",
@@ -1652,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",