namespace: properly handle bind mounts from the host
authorLennart Poettering <lennart@poettering.net>
Thu, 28 Sep 2017 16:30:55 +0000 (18:30 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 2 Oct 2017 15:41:43 +0000 (17:41 +0200)
Before this patch we had an ordering problem: if we have no namespacing
enabled except for two bind mounts that intend to swap /a and /b via
bind mounts, then we'd execute the bind mount binding /b to /a, followed
by thebind mount from /a to /b, thus having the effect that /b is now
visible in both /a and /b, which was not intended.

With this change, as soon as any bind mount is configured we'll put
together the service mount namespace in a temporary directory instead of
operating directly in the root. This solves the problem in a
straightforward fashion: the source of bind mounts will always refer to
the host, and thus be unaffected from the bind mounts we already
created.

src/core/namespace.c

index 8511131..069e0ed 100644 (file)
@@ -31,6 +31,7 @@
 #include "dev-setup.h"
 #include "fd-util.h"
 #include "fs-util.h"
+#include "label.h"
 #include "loop-util.h"
 #include "loopback-setup.h"
 #include "missing.h"
@@ -904,6 +905,7 @@ int setup_namespace(
         MountEntry *m, *mounts = NULL;
         size_t root_hash_size = 0;
         bool make_slave = false;
+        const char *root;
         unsigned n_mounts;
         int r = 0;
 
@@ -935,18 +937,25 @@ int setup_namespace(
                 r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, dissect_image_flags, &decrypted_image);
                 if (r < 0)
                         return r;
-
-                if (!root_directory) {
-                        /* 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. */
-                        root_directory = "/run/systemd/unit-root";
-                        (void) mkdir(root_directory, 0700);
-                }
         }
 
+        if (root_directory)
+                root = root_directory;
+        else if (root_image || n_bind_mounts > 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. */
+
+                root = "/run/systemd/unit-root";
+                (void) mkdir_label(root, 0700);
+        } else
+                root = NULL;
+
         n_mounts = namespace_calculate_mounts(
-                        root_directory,
+                        root,
                         ns_info,
                         read_write_paths,
                         read_only_paths,
@@ -956,7 +965,7 @@ int setup_namespace(
                         protect_home, protect_system);
 
         /* Set mount slave mode */
-        if (root_directory || n_mounts > 0)
+        if (root || n_mounts > 0)
                 make_slave = true;
 
         if (n_mounts > 0) {
@@ -1025,7 +1034,7 @@ int setup_namespace(
                 if (r < 0)
                         goto finish;
 
-                if (namespace_info_mount_apivfs(root_directory, ns_info)) {
+                if (namespace_info_mount_apivfs(root, ns_info)) {
                         r = append_static_mounts(&m, apivfs_table, ELEMENTSOF(apivfs_table), ns_info->ignore_protect_paths);
                         if (r < 0)
                                 goto finish;
@@ -1034,14 +1043,14 @@ int setup_namespace(
                 assert(mounts + n_mounts == m);
 
                 /* Prepend the root directory where that's necessary */
-                r = prefix_where_needed(mounts, n_mounts, root_directory);
+                r = prefix_where_needed(mounts, n_mounts, root);
                 if (r < 0)
                         goto finish;
 
                 qsort(mounts, n_mounts, sizeof(MountEntry), mount_path_compare);
 
                 drop_duplicates(mounts, &n_mounts);
-                drop_outside_root(root_directory, mounts, &n_mounts);
+                drop_outside_root(root, mounts, &n_mounts);
                 drop_inaccessible(mounts, &n_mounts);
                 drop_nop(mounts, &n_mounts);
         }
@@ -1061,11 +1070,12 @@ int setup_namespace(
         }
 
         /* Try to set up the new root directory before mounting anything there */
-        if (root_directory)
-                (void) base_filesystem_create(root_directory, UID_INVALID, GID_INVALID);
+        if (root)
+                (void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
 
         if (root_image) {
-                r = dissected_image_mount(dissected_image, root_directory, dissect_image_flags);
+                /* A root image is specified, mount it to the right place */
+                r = dissected_image_mount(dissected_image, root, dissect_image_flags);
                 if (r < 0)
                         goto finish;
 
@@ -1079,16 +1089,24 @@ int setup_namespace(
 
         } else if (root_directory) {
 
-                /* Turn directory into bind mount, if it isn't one yet */
-                r = path_is_mount_point(root_directory, NULL, AT_SYMLINK_FOLLOW);
+                /* 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)
                         goto finish;
                 if (r == 0) {
-                        if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) {
+                        if (mount(root, root, NULL, MS_BIND|MS_REC, NULL) < 0) {
                                 r = -errno;
                                 goto finish;
                         }
                 }
+
+        } else if (root) {
+
+                /* 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;
+                        goto finish;
+                }
         }
 
         if (n_mounts > 0) {
@@ -1106,7 +1124,7 @@ int setup_namespace(
 
                 /* First round, add in all special mounts we need */
                 for (m = mounts; m < mounts + n_mounts; ++m) {
-                        r = apply_mount(root_directory, m, tmp_dir, var_tmp_dir);
+                        r = apply_mount(root, m, tmp_dir, var_tmp_dir);
                         if (r < 0)
                                 goto finish;
                 }
@@ -1125,9 +1143,9 @@ int setup_namespace(
                 }
         }
 
-        if (root_directory) {
+        if (root) {
                 /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
-                r = mount_move_root(root_directory);
+                r = mount_move_root(root);
                 if (r < 0)
                         goto finish;
         }