chiark / gitweb /
copy: extend check for mount point crossing
authorLennart Poettering <lennart@poettering.net>
Wed, 6 Jun 2018 15:33:28 +0000 (17:33 +0200)
committerSven Eden <yamakuzure@gmx.net>
Fri, 24 Aug 2018 14:47:08 +0000 (16:47 +0200)
We do this checks as protection against bind mount cycles on the same
file system. However, the check wasn't really effective for that, as
it would only detect cycles A → B → A this way. By using
fs_is_mount_point() we'll also detect cycles A → A.

Also, while we are at it, make these file system boundary checks
optional. This is not used anywhere, but might be eventually...

Most importantly though add a longer blurb explanation the why.

src/basic/copy.c
src/basic/copy.h

index 5962e4ce732a7cfad179e2f9cfe76e91951d3e3a..c09292b944a946f1f4b5bd5c1e94d0c3b43d4d85 100644 (file)
@@ -29,6 +29,7 @@
 #include "io-util.h"
 //#include "macro.h"
 #include "missing.h"
+//#include "mount-util.h"
 //#include "string-util.h"
 #include "strv.h"
 #include "time-util.h"
@@ -534,8 +535,34 @@ static int fd_copy_directory(
                 }
 
                 if (S_ISDIR(buf.st_mode)) {
-                        if (buf.st_dev != original_device)
-                                continue;
+                        /*
+                         * Don't descend into directories on other file systems, if this is requested. We do a simple
+                         * .st_dev check here, which basically comes for free. Note that we do this check only on
+                         * directories, not other kind of file system objects, for two reason:
+                         *
+                         * • The kernel's overlayfs pseudo file system that overlays multiple real file systems
+                         *   propagates the .st_dev field of the file system a file originates from all the way up
+                         *   through the stack to stat(). It doesn't do that for directories however. This means that
+                         *   comparing .st_dev on non-directories suggests that they all are mount points. To avoid
+                         *   confusion we hence avoid relying on this check for regular files.
+                         *
+                         * • The main reason we do this check at all is to protect ourselves from bind mount cycles,
+                         *   where we really want to avoid descending down in all eternity. However the .st_dev check
+                         *   is usually not sufficient for this protection anyway, as bind mount cycles from the same
+                         *   file system onto itself can't be detected that way.
+                         */
+
+                        if (FLAGS_SET(copy_flags, COPY_SAME_MOUNT)) {
+                                if (buf.st_dev != original_device)
+                                        continue;
+
+                                r = fd_is_mount_point(dirfd(d), de->d_name, 0);
+                                if (r < 0)
+                                        return r;
+                                if (r > 0)
+                                        continue;
+                        }
+
                         q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, override_uid, override_gid, copy_flags);
                 } else if (S_ISREG(buf.st_mode))
                         q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name, override_uid, override_gid, copy_flags);
index 8f5acdb7ce7b623148351d4752da63915eb797b4..7c1d86fe365f86d2388d8e5348b5874bbbacf0ab 100644 (file)
 #include <sys/types.h>
 
 typedef enum CopyFlags {
-        COPY_REFLINK = 1U << 0, /* Try to reflink */
-        COPY_MERGE   = 1U << 1, /* Merge existing trees with our new one to copy */
-        COPY_REPLACE = 1U << 2, /* Replace an existing file if there's one */
+        COPY_REFLINK    = 1U << 0, /* Try to reflink */
+        COPY_MERGE      = 1U << 1, /* Merge existing trees with our new one to copy */
+        COPY_REPLACE    = 1U << 2, /* Replace an existing file if there's one */
+        COPY_SAME_MOUNT = 1U << 3, /* Don't descend recursively into other file systems, across mount point boundaries */
 } CopyFlags;
 
 #if 0 /// UNNEEDED by elogind