Merge tag 'fs.mount_setattr.nosymfollow.v5.14' of git://git.kernel.org/pub/scm/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 30 Jun 2021 03:07:06 +0000 (20:07 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 30 Jun 2021 03:07:06 +0000 (20:07 -0700)
Pull mount_setattr updates from Christian Brauner:
 "A few releases ago the old mount API gained support for a mount
  options which prevents following symlinks on a given mount. This adds
  support for it in the new mount api through the MOUNT_ATTR_NOSYMFOLLOW
  flag via mount_setattr() and fsmount(). With mount_setattr() that flag
  can even be applied recursively.

  There's an additional ack from Ross Zwisler who originally authored
  the nosymfollow patch. As I've already had the patches in my for-next
  I didn't add his ack explicitly"

* tag 'fs.mount_setattr.nosymfollow.v5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  tests: test MOUNT_ATTR_NOSYMFOLLOW with mount_setattr()
  mount: Support "nosymfollow" in new mount api

fs/namespace.c
include/uapi/linux/mount.h
tools/testing/selftests/mount_setattr/mount_setattr_test.c

index c3f1a78..ab4174a 100644 (file)
@@ -3464,9 +3464,10 @@ out_type:
        return ret;
 }
 
-#define FSMOUNT_VALID_FLAGS \
-       (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \
-        MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME)
+#define FSMOUNT_VALID_FLAGS                                                    \
+       (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV |            \
+        MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME |       \
+        MOUNT_ATTR_NOSYMFOLLOW)
 
 #define MOUNT_SETATTR_VALID_FLAGS (FSMOUNT_VALID_FLAGS | MOUNT_ATTR_IDMAP)
 
@@ -3487,6 +3488,8 @@ static unsigned int attr_flags_to_mnt_flags(u64 attr_flags)
                mnt_flags |= MNT_NOEXEC;
        if (attr_flags & MOUNT_ATTR_NODIRATIME)
                mnt_flags |= MNT_NODIRATIME;
+       if (attr_flags & MOUNT_ATTR_NOSYMFOLLOW)
+               mnt_flags |= MNT_NOSYMFOLLOW;
 
        return mnt_flags;
 }
index e6524ea..dd7a166 100644 (file)
@@ -120,6 +120,7 @@ enum fsconfig_command {
 #define MOUNT_ATTR_STRICTATIME 0x00000020 /* - Always perform atime updates */
 #define MOUNT_ATTR_NODIRATIME  0x00000080 /* Do not update directory access times */
 #define MOUNT_ATTR_IDMAP       0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */
+#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000 /* Do not follow symlinks */
 
 /*
  * mount_setattr()
index 4e94e56..f31205f 100644 (file)
@@ -136,6 +136,10 @@ struct mount_attr {
 #define MOUNT_ATTR_IDMAP 0x00100000
 #endif
 
+#ifndef MOUNT_ATTR_NOSYMFOLLOW
+#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000
+#endif
+
 static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags,
                                    struct mount_attr *attr, size_t size)
 {
@@ -235,6 +239,10 @@ static int prepare_unpriv_mountns(void)
        return 0;
 }
 
+#ifndef ST_NOSYMFOLLOW
+#define ST_NOSYMFOLLOW 0x2000 /* do not follow symlinks */
+#endif
+
 static int read_mnt_flags(const char *path)
 {
        int ret;
@@ -245,9 +253,9 @@ static int read_mnt_flags(const char *path)
        if (ret != 0)
                return -EINVAL;
 
-       if (stat.f_flag &
-           ~(ST_RDONLY | ST_NOSUID | ST_NODEV | ST_NOEXEC | ST_NOATIME |
-             ST_NODIRATIME | ST_RELATIME | ST_SYNCHRONOUS | ST_MANDLOCK))
+       if (stat.f_flag & ~(ST_RDONLY | ST_NOSUID | ST_NODEV | ST_NOEXEC |
+                           ST_NOATIME | ST_NODIRATIME | ST_RELATIME |
+                           ST_SYNCHRONOUS | ST_MANDLOCK | ST_NOSYMFOLLOW))
                return -EINVAL;
 
        mnt_flags = 0;
@@ -269,6 +277,8 @@ static int read_mnt_flags(const char *path)
                mnt_flags |= MS_SYNCHRONOUS;
        if (stat.f_flag & ST_MANDLOCK)
                mnt_flags |= ST_MANDLOCK;
+       if (stat.f_flag & ST_NOSYMFOLLOW)
+               mnt_flags |= ST_NOSYMFOLLOW;
 
        return mnt_flags;
 }
@@ -368,8 +378,13 @@ static bool mount_setattr_supported(void)
 FIXTURE(mount_setattr) {
 };
 
+#define NOSYMFOLLOW_TARGET "/mnt/A/AA/data"
+#define NOSYMFOLLOW_SYMLINK "/mnt/A/AA/symlink"
+
 FIXTURE_SETUP(mount_setattr)
 {
+       int fd = -EBADF;
+
        if (!mount_setattr_supported())
                SKIP(return, "mount_setattr syscall not supported");
 
@@ -412,6 +427,11 @@ FIXTURE_SETUP(mount_setattr)
 
        ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts",
                        MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0);
+
+       fd = creat(NOSYMFOLLOW_TARGET, O_RDWR | O_CLOEXEC);
+       ASSERT_GT(fd, 0);
+       ASSERT_EQ(symlink(NOSYMFOLLOW_TARGET, NOSYMFOLLOW_SYMLINK), 0);
+       ASSERT_EQ(close(fd), 0);
 }
 
 FIXTURE_TEARDOWN(mount_setattr)
@@ -1421,4 +1441,66 @@ TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid)
        ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/BB/b", 0, 0, 0), 0);
 }
 
+TEST_F(mount_setattr, mount_attr_nosymfollow)
+{
+       int fd;
+       unsigned int old_flags = 0, new_flags = 0, expected_flags = 0;
+       struct mount_attr attr = {
+               .attr_set       = MOUNT_ATTR_NOSYMFOLLOW,
+       };
+
+       if (!mount_setattr_supported())
+               SKIP(return, "mount_setattr syscall not supported");
+
+       fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
+       ASSERT_GT(fd, 0);
+       ASSERT_EQ(close(fd), 0);
+
+       old_flags = read_mnt_flags("/mnt/A");
+       ASSERT_GT(old_flags, 0);
+
+       ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
+
+       expected_flags = old_flags;
+       expected_flags |= ST_NOSYMFOLLOW;
+
+       new_flags = read_mnt_flags("/mnt/A");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA/B");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
+       ASSERT_LT(fd, 0);
+       ASSERT_EQ(errno, ELOOP);
+
+       attr.attr_set &= ~MOUNT_ATTR_NOSYMFOLLOW;
+       attr.attr_clr |= MOUNT_ATTR_NOSYMFOLLOW;
+
+       ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0);
+
+       expected_flags &= ~ST_NOSYMFOLLOW;
+       new_flags = read_mnt_flags("/mnt/A");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA/B");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       new_flags = read_mnt_flags("/mnt/A/AA/B/BB");
+       ASSERT_EQ(new_flags, expected_flags);
+
+       fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC);
+       ASSERT_GT(fd, 0);
+       ASSERT_EQ(close(fd), 0);
+}
+
 TEST_HARNESS_MAIN