| // SPDX-License-Identifier: GPL-2.0 |
| #define _GNU_SOURCE |
| #include <sched.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/mount.h> |
| #include <sys/wait.h> |
| #include <sys/vfs.h> |
| #include <sys/statvfs.h> |
| #include <sys/sysinfo.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <grp.h> |
| #include <stdbool.h> |
| #include <stdarg.h> |
| |
| #include "../kselftest_harness.h" |
| |
| #ifndef CLONE_NEWNS |
| #define CLONE_NEWNS 0x00020000 |
| #endif |
| |
| #ifndef CLONE_NEWUSER |
| #define CLONE_NEWUSER 0x10000000 |
| #endif |
| |
| #ifndef MS_REC |
| #define MS_REC 16384 |
| #endif |
| |
| #ifndef MS_RELATIME |
| #define MS_RELATIME (1 << 21) |
| #endif |
| |
| #ifndef MS_STRICTATIME |
| #define MS_STRICTATIME (1 << 24) |
| #endif |
| |
| #ifndef MOUNT_ATTR_RDONLY |
| #define MOUNT_ATTR_RDONLY 0x00000001 |
| #endif |
| |
| #ifndef MOUNT_ATTR_NOSUID |
| #define MOUNT_ATTR_NOSUID 0x00000002 |
| #endif |
| |
| #ifndef MOUNT_ATTR_NOEXEC |
| #define MOUNT_ATTR_NOEXEC 0x00000008 |
| #endif |
| |
| #ifndef MOUNT_ATTR_NODIRATIME |
| #define MOUNT_ATTR_NODIRATIME 0x00000080 |
| #endif |
| |
| #ifndef MOUNT_ATTR__ATIME |
| #define MOUNT_ATTR__ATIME 0x00000070 |
| #endif |
| |
| #ifndef MOUNT_ATTR_RELATIME |
| #define MOUNT_ATTR_RELATIME 0x00000000 |
| #endif |
| |
| #ifndef MOUNT_ATTR_NOATIME |
| #define MOUNT_ATTR_NOATIME 0x00000010 |
| #endif |
| |
| #ifndef MOUNT_ATTR_STRICTATIME |
| #define MOUNT_ATTR_STRICTATIME 0x00000020 |
| #endif |
| |
| #ifndef AT_RECURSIVE |
| #define AT_RECURSIVE 0x8000 |
| #endif |
| |
| #ifndef MS_SHARED |
| #define MS_SHARED (1 << 20) |
| #endif |
| |
| #define DEFAULT_THREADS 4 |
| #define ptr_to_int(p) ((int)((intptr_t)(p))) |
| #define int_to_ptr(u) ((void *)((intptr_t)(u))) |
| |
| #ifndef __NR_mount_setattr |
| #if defined __alpha__ |
| #define __NR_mount_setattr 552 |
| #elif defined _MIPS_SIM |
| #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */ |
| #define __NR_mount_setattr (442 + 4000) |
| #endif |
| #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */ |
| #define __NR_mount_setattr (442 + 6000) |
| #endif |
| #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */ |
| #define __NR_mount_setattr (442 + 5000) |
| #endif |
| #elif defined __ia64__ |
| #define __NR_mount_setattr (442 + 1024) |
| #else |
| #define __NR_mount_setattr 442 |
| #endif |
| |
| struct mount_attr { |
| __u64 attr_set; |
| __u64 attr_clr; |
| __u64 propagation; |
| __u64 userns_fd; |
| }; |
| #endif |
| |
| #ifndef __NR_open_tree |
| #if defined __alpha__ |
| #define __NR_open_tree 538 |
| #elif defined _MIPS_SIM |
| #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */ |
| #define __NR_open_tree 4428 |
| #endif |
| #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */ |
| #define __NR_open_tree 6428 |
| #endif |
| #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */ |
| #define __NR_open_tree 5428 |
| #endif |
| #elif defined __ia64__ |
| #define __NR_open_tree (428 + 1024) |
| #else |
| #define __NR_open_tree 428 |
| #endif |
| #endif |
| |
| #ifndef MOUNT_ATTR_IDMAP |
| #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) |
| { |
| return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); |
| } |
| |
| #ifndef OPEN_TREE_CLONE |
| #define OPEN_TREE_CLONE 1 |
| #endif |
| |
| #ifndef OPEN_TREE_CLOEXEC |
| #define OPEN_TREE_CLOEXEC O_CLOEXEC |
| #endif |
| |
| #ifndef AT_RECURSIVE |
| #define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */ |
| #endif |
| |
| static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags) |
| { |
| return syscall(__NR_open_tree, dfd, filename, flags); |
| } |
| |
| static ssize_t write_nointr(int fd, const void *buf, size_t count) |
| { |
| ssize_t ret; |
| |
| do { |
| ret = write(fd, buf, count); |
| } while (ret < 0 && errno == EINTR); |
| |
| return ret; |
| } |
| |
| static int write_file(const char *path, const void *buf, size_t count) |
| { |
| int fd; |
| ssize_t ret; |
| |
| fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); |
| if (fd < 0) |
| return -1; |
| |
| ret = write_nointr(fd, buf, count); |
| close(fd); |
| if (ret < 0 || (size_t)ret != count) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int create_and_enter_userns(void) |
| { |
| uid_t uid; |
| gid_t gid; |
| char map[100]; |
| |
| uid = getuid(); |
| gid = getgid(); |
| |
| if (unshare(CLONE_NEWUSER)) |
| return -1; |
| |
| if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) && |
| errno != ENOENT) |
| return -1; |
| |
| snprintf(map, sizeof(map), "0 %d 1", uid); |
| if (write_file("/proc/self/uid_map", map, strlen(map))) |
| return -1; |
| |
| |
| snprintf(map, sizeof(map), "0 %d 1", gid); |
| if (write_file("/proc/self/gid_map", map, strlen(map))) |
| return -1; |
| |
| if (setgid(0)) |
| return -1; |
| |
| if (setuid(0)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int prepare_unpriv_mountns(void) |
| { |
| if (create_and_enter_userns()) |
| return -1; |
| |
| if (unshare(CLONE_NEWNS)) |
| return -1; |
| |
| if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) |
| return -1; |
| |
| return 0; |
| } |
| |
| #ifndef ST_NOSYMFOLLOW |
| #define ST_NOSYMFOLLOW 0x2000 /* do not follow symlinks */ |
| #endif |
| |
| static int read_mnt_flags(const char *path) |
| { |
| int ret; |
| struct statvfs stat; |
| unsigned int mnt_flags; |
| |
| ret = statvfs(path, &stat); |
| 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 | ST_NOSYMFOLLOW)) |
| return -EINVAL; |
| |
| mnt_flags = 0; |
| if (stat.f_flag & ST_RDONLY) |
| mnt_flags |= MS_RDONLY; |
| if (stat.f_flag & ST_NOSUID) |
| mnt_flags |= MS_NOSUID; |
| if (stat.f_flag & ST_NODEV) |
| mnt_flags |= MS_NODEV; |
| if (stat.f_flag & ST_NOEXEC) |
| mnt_flags |= MS_NOEXEC; |
| if (stat.f_flag & ST_NOATIME) |
| mnt_flags |= MS_NOATIME; |
| if (stat.f_flag & ST_NODIRATIME) |
| mnt_flags |= MS_NODIRATIME; |
| if (stat.f_flag & ST_RELATIME) |
| mnt_flags |= MS_RELATIME; |
| if (stat.f_flag & ST_SYNCHRONOUS) |
| 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; |
| } |
| |
| static char *get_field(char *src, int nfields) |
| { |
| int i; |
| char *p = src; |
| |
| for (i = 0; i < nfields; i++) { |
| while (*p && *p != ' ' && *p != '\t') |
| p++; |
| |
| if (!*p) |
| break; |
| |
| p++; |
| } |
| |
| return p; |
| } |
| |
| static void null_endofword(char *word) |
| { |
| while (*word && *word != ' ' && *word != '\t') |
| word++; |
| *word = '\0'; |
| } |
| |
| static bool is_shared_mount(const char *path) |
| { |
| size_t len = 0; |
| char *line = NULL; |
| FILE *f = NULL; |
| |
| f = fopen("/proc/self/mountinfo", "re"); |
| if (!f) |
| return false; |
| |
| while (getline(&line, &len, f) != -1) { |
| char *opts, *target; |
| |
| target = get_field(line, 4); |
| if (!target) |
| continue; |
| |
| opts = get_field(target, 2); |
| if (!opts) |
| continue; |
| |
| null_endofword(target); |
| |
| if (strcmp(target, path) != 0) |
| continue; |
| |
| null_endofword(opts); |
| if (strstr(opts, "shared:")) |
| return true; |
| } |
| |
| free(line); |
| fclose(f); |
| |
| return false; |
| } |
| |
| static void *mount_setattr_thread(void *data) |
| { |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID, |
| .attr_clr = 0, |
| .propagation = MS_SHARED, |
| }; |
| |
| if (sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr))) |
| pthread_exit(int_to_ptr(-1)); |
| |
| pthread_exit(int_to_ptr(0)); |
| } |
| |
| /* Attempt to de-conflict with the selftests tree. */ |
| #ifndef SKIP |
| #define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__) |
| #endif |
| |
| static bool mount_setattr_supported(void) |
| { |
| int ret; |
| |
| ret = sys_mount_setattr(-EBADF, "", AT_EMPTY_PATH, NULL, 0); |
| if (ret < 0 && errno == ENOSYS) |
| return false; |
| |
| return true; |
| } |
| |
| 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"); |
| |
| ASSERT_EQ(prepare_unpriv_mountns(), 0); |
| |
| (void)umount2("/mnt", MNT_DETACH); |
| (void)umount2("/tmp", MNT_DETACH); |
| |
| ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/tmp/B", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/A", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0); |
| |
| ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/B", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt/B", "ramfs", |
| MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0); |
| |
| 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) |
| { |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| (void)umount2("/mnt/A", MNT_DETACH); |
| (void)umount2("/tmp", MNT_DETACH); |
| } |
| |
| TEST_F(mount_setattr, invalid_attributes) |
| { |
| struct mount_attr invalid_attr = { |
| .attr_set = (1U << 31), |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr)), 0); |
| |
| invalid_attr.attr_set = 0; |
| invalid_attr.attr_clr = (1U << 31); |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr)), 0); |
| |
| invalid_attr.attr_clr = 0; |
| invalid_attr.propagation = (1U << 31); |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr)), 0); |
| |
| invalid_attr.attr_set = (1U << 31); |
| invalid_attr.attr_clr = (1U << 31); |
| invalid_attr.propagation = (1U << 31); |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr)), 0); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr)), 0); |
| } |
| |
| TEST_F(mount_setattr, extensibility) |
| { |
| unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; |
| char *s = "dummy"; |
| struct mount_attr invalid_attr = {}; |
| struct mount_attr_large { |
| struct mount_attr attr1; |
| struct mount_attr attr2; |
| struct mount_attr attr3; |
| } large_attr = {}; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| old_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_GT(old_flags, 0); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, NULL, |
| sizeof(invalid_attr)), 0); |
| ASSERT_EQ(errno, EFAULT); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, (void *)s, |
| sizeof(invalid_attr)), 0); |
| ASSERT_EQ(errno, EINVAL); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, 0), 0); |
| ASSERT_EQ(errno, EINVAL); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr) / 2), 0); |
| ASSERT_EQ(errno, EINVAL); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, |
| sizeof(invalid_attr) / 2), 0); |
| ASSERT_EQ(errno, EINVAL); |
| |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, |
| (void *)&large_attr, sizeof(large_attr)), 0); |
| |
| large_attr.attr3.attr_set = MOUNT_ATTR_RDONLY; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, |
| (void *)&large_attr, sizeof(large_attr)), 0); |
| |
| large_attr.attr3.attr_set = 0; |
| large_attr.attr1.attr_set = MOUNT_ATTR_RDONLY; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, |
| (void *)&large_attr, sizeof(large_attr)), 0); |
| |
| expected_flags = old_flags; |
| expected_flags |= MS_RDONLY; |
| |
| 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); |
| } |
| |
| TEST_F(mount_setattr, basic) |
| { |
| unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, |
| .attr_clr = MOUNT_ATTR__ATIME, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| old_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_GT(old_flags, 0); |
| |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", 0, &attr, sizeof(attr)), 0); |
| |
| expected_flags = old_flags; |
| expected_flags |= MS_RDONLY; |
| expected_flags |= MS_NOEXEC; |
| expected_flags &= ~MS_NOATIME; |
| expected_flags |= MS_RELATIME; |
| |
| 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, old_flags); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B"); |
| ASSERT_EQ(new_flags, old_flags); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); |
| ASSERT_EQ(new_flags, old_flags); |
| } |
| |
| TEST_F(mount_setattr, basic_recursive) |
| { |
| int fd; |
| unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, |
| .attr_clr = MOUNT_ATTR__ATIME, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| 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 |= MS_RDONLY; |
| expected_flags |= MS_NOEXEC; |
| expected_flags &= ~MS_NOATIME; |
| expected_flags |= MS_RELATIME; |
| |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.attr_clr = MOUNT_ATTR_RDONLY; |
| attr.propagation = MS_SHARED; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags &= ~MS_RDONLY; |
| new_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); |
| |
| fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777); |
| ASSERT_GE(fd, 0); |
| |
| /* |
| * We're holding a fd open for writing so this needs to fail somewhere |
| * in the middle and the mount options need to be unchanged. |
| */ |
| attr.attr_set = MOUNT_ATTR_RDONLY; |
| ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| new_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); |
| |
| EXPECT_EQ(close(fd), 0); |
| } |
| |
| TEST_F(mount_setattr, mount_has_writers) |
| { |
| int fd, dfd; |
| unsigned int old_flags = 0, new_flags = 0; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, |
| .attr_clr = MOUNT_ATTR__ATIME, |
| .propagation = MS_SHARED, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| old_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_GT(old_flags, 0); |
| |
| fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777); |
| ASSERT_GE(fd, 0); |
| |
| /* |
| * We're holding a fd open to a mount somwhere in the middle so this |
| * needs to fail somewhere in the middle. After this the mount options |
| * need to be unchanged. |
| */ |
| ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| new_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_EQ(new_flags, old_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A"), false); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA"); |
| ASSERT_EQ(new_flags, old_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA"), false); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B"); |
| ASSERT_EQ(new_flags, old_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), false); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); |
| ASSERT_EQ(new_flags, old_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), false); |
| |
| dfd = open("/mnt/A/AA/B", O_DIRECTORY | O_CLOEXEC); |
| ASSERT_GE(dfd, 0); |
| EXPECT_EQ(fsync(dfd), 0); |
| EXPECT_EQ(close(dfd), 0); |
| |
| EXPECT_EQ(fsync(fd), 0); |
| EXPECT_EQ(close(fd), 0); |
| |
| /* All writers are gone so this should succeed. */ |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| } |
| |
| TEST_F(mount_setattr, mixed_mount_options) |
| { |
| unsigned int old_flags1 = 0, old_flags2 = 0, new_flags = 0, expected_flags = 0; |
| struct mount_attr attr = { |
| .attr_clr = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME, |
| .attr_set = MOUNT_ATTR_RELATIME, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| old_flags1 = read_mnt_flags("/mnt/B"); |
| ASSERT_GT(old_flags1, 0); |
| |
| old_flags2 = read_mnt_flags("/mnt/B/BB"); |
| ASSERT_GT(old_flags2, 0); |
| |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/B", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags = old_flags2; |
| expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID); |
| expected_flags |= MS_RELATIME; |
| |
| new_flags = read_mnt_flags("/mnt/B"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| expected_flags = old_flags2; |
| expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID); |
| expected_flags |= MS_RELATIME; |
| |
| new_flags = read_mnt_flags("/mnt/B/BB"); |
| ASSERT_EQ(new_flags, expected_flags); |
| } |
| |
| TEST_F(mount_setattr, time_changes) |
| { |
| unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| attr.attr_set = MOUNT_ATTR_STRICTATIME; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME; |
| attr.attr_clr = MOUNT_ATTR__ATIME; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| attr.attr_set = 0; |
| attr.attr_clr = MOUNT_ATTR_STRICTATIME; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| attr.attr_clr = MOUNT_ATTR_NOATIME; |
| ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| old_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_GT(old_flags, 0); |
| |
| attr.attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME; |
| attr.attr_clr = MOUNT_ATTR__ATIME; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags = old_flags; |
| expected_flags |= MS_NOATIME; |
| expected_flags |= MS_NODIRATIME; |
| |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.attr_set &= ~MOUNT_ATTR_NOATIME; |
| attr.attr_set |= MOUNT_ATTR_RELATIME; |
| attr.attr_clr |= MOUNT_ATTR__ATIME; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags &= ~MS_NOATIME; |
| expected_flags |= MS_RELATIME; |
| |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.attr_set &= ~MOUNT_ATTR_RELATIME; |
| attr.attr_set |= MOUNT_ATTR_STRICTATIME; |
| attr.attr_clr |= MOUNT_ATTR__ATIME; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags &= ~MS_RELATIME; |
| |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.attr_set &= ~MOUNT_ATTR_STRICTATIME; |
| attr.attr_set |= MOUNT_ATTR_NOATIME; |
| attr.attr_clr |= MOUNT_ATTR__ATIME; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags |= MS_NOATIME; |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| 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); |
| |
| memset(&attr, 0, sizeof(attr)); |
| attr.attr_clr = MOUNT_ATTR_NODIRATIME; |
| ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); |
| |
| expected_flags &= ~MS_NODIRATIME; |
| |
| 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); |
| } |
| |
| TEST_F(mount_setattr, multi_threaded) |
| { |
| int i, j, nthreads, ret = 0; |
| unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; |
| pthread_attr_t pattr; |
| pthread_t threads[DEFAULT_THREADS]; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| old_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_GT(old_flags, 0); |
| |
| /* Try to change mount options from multiple threads. */ |
| nthreads = get_nprocs_conf(); |
| if (nthreads > DEFAULT_THREADS) |
| nthreads = DEFAULT_THREADS; |
| |
| pthread_attr_init(&pattr); |
| for (i = 0; i < nthreads; i++) |
| ASSERT_EQ(pthread_create(&threads[i], &pattr, mount_setattr_thread, NULL), 0); |
| |
| for (j = 0; j < i; j++) { |
| void *retptr = NULL; |
| |
| EXPECT_EQ(pthread_join(threads[j], &retptr), 0); |
| |
| ret += ptr_to_int(retptr); |
| EXPECT_EQ(ret, 0); |
| } |
| pthread_attr_destroy(&pattr); |
| |
| ASSERT_EQ(ret, 0); |
| |
| expected_flags = old_flags; |
| expected_flags |= MS_RDONLY; |
| expected_flags |= MS_NOSUID; |
| new_flags = read_mnt_flags("/mnt/A"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); |
| |
| new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); |
| ASSERT_EQ(new_flags, expected_flags); |
| |
| ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); |
| } |
| |
| TEST_F(mount_setattr, wrong_user_namespace) |
| { |
| int ret; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| EXPECT_EQ(create_and_enter_userns(), 0); |
| ret = sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)); |
| ASSERT_LT(ret, 0); |
| ASSERT_EQ(errno, EPERM); |
| } |
| |
| TEST_F(mount_setattr, wrong_mount_namespace) |
| { |
| int fd, ret; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_RDONLY, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| fd = open("/mnt/A", O_DIRECTORY | O_CLOEXEC); |
| ASSERT_GE(fd, 0); |
| |
| ASSERT_EQ(unshare(CLONE_NEWNS), 0); |
| |
| ret = sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)); |
| ASSERT_LT(ret, 0); |
| ASSERT_EQ(errno, EINVAL); |
| } |
| |
| FIXTURE(mount_setattr_idmapped) { |
| }; |
| |
| FIXTURE_SETUP(mount_setattr_idmapped) |
| { |
| int img_fd = -EBADF; |
| |
| ASSERT_EQ(unshare(CLONE_NEWNS), 0); |
| |
| ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0), 0); |
| |
| (void)umount2("/mnt", MNT_DETACH); |
| (void)umount2("/tmp", MNT_DETACH); |
| |
| ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/tmp/B", 0777), 0); |
| ASSERT_EQ(mknodat(-EBADF, "/tmp/B/b", S_IFREG | 0644, 0), 0); |
| ASSERT_EQ(chown("/tmp/B/b", 0, 0), 0); |
| |
| ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0); |
| ASSERT_EQ(mknodat(-EBADF, "/tmp/B/BB/b", S_IFREG | 0644, 0), 0); |
| ASSERT_EQ(chown("/tmp/B/BB/b", 0, 0), 0); |
| |
| ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/A", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV, |
| "size=100000,mode=700"), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0); |
| |
| ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/B", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/mnt/B", "ramfs", |
| MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0); |
| |
| ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts", |
| MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0); |
| |
| ASSERT_EQ(mkdir("/mnt/C", 0777), 0); |
| ASSERT_EQ(mkdir("/mnt/D", 0777), 0); |
| img_fd = openat(-EBADF, "/mnt/C/ext4.img", O_CREAT | O_WRONLY, 0600); |
| ASSERT_GE(img_fd, 0); |
| ASSERT_EQ(ftruncate(img_fd, 1024 * 2048), 0); |
| ASSERT_EQ(system("mkfs.ext4 -q /mnt/C/ext4.img"), 0); |
| ASSERT_EQ(system("mount -o loop -t ext4 /mnt/C/ext4.img /mnt/D/"), 0); |
| ASSERT_EQ(close(img_fd), 0); |
| } |
| |
| FIXTURE_TEARDOWN(mount_setattr_idmapped) |
| { |
| (void)umount2("/mnt/A", MNT_DETACH); |
| (void)umount2("/tmp", MNT_DETACH); |
| } |
| |
| /** |
| * Validate that negative fd values are rejected. |
| */ |
| TEST_F(mount_setattr_idmapped, invalid_fd_negative) |
| { |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = -EBADF, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { |
| TH_LOG("failure: created idmapped mount with negative fd"); |
| } |
| } |
| |
| /** |
| * Validate that excessively large fd values are rejected. |
| */ |
| TEST_F(mount_setattr_idmapped, invalid_fd_large) |
| { |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| .userns_fd = INT64_MAX, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { |
| TH_LOG("failure: created idmapped mount with too large fd value"); |
| } |
| } |
| |
| /** |
| * Validate that closed fd values are rejected. |
| */ |
| TEST_F(mount_setattr_idmapped, invalid_fd_closed) |
| { |
| int fd; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| fd = open("/dev/null", O_RDONLY | O_CLOEXEC); |
| ASSERT_GE(fd, 0); |
| ASSERT_GE(close(fd), 0); |
| |
| attr.userns_fd = fd; |
| ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { |
| TH_LOG("failure: created idmapped mount with closed fd"); |
| } |
| } |
| |
| /** |
| * Validate that the initial user namespace is rejected. |
| */ |
| TEST_F(mount_setattr_idmapped, invalid_fd_initial_userns) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| attr.userns_fd = open("/proc/1/ns/user", O_RDONLY | O_CLOEXEC); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(errno, EPERM); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| static int map_ids(pid_t pid, unsigned long nsid, unsigned long hostid, |
| unsigned long range) |
| { |
| char map[100], procfile[256]; |
| |
| snprintf(procfile, sizeof(procfile), "/proc/%d/uid_map", pid); |
| snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range); |
| if (write_file(procfile, map, strlen(map))) |
| return -1; |
| |
| |
| snprintf(procfile, sizeof(procfile), "/proc/%d/gid_map", pid); |
| snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range); |
| if (write_file(procfile, map, strlen(map))) |
| return -1; |
| |
| return 0; |
| } |
| |
| #define __STACK_SIZE (8 * 1024 * 1024) |
| static pid_t do_clone(int (*fn)(void *), void *arg, int flags) |
| { |
| void *stack; |
| |
| stack = malloc(__STACK_SIZE); |
| if (!stack) |
| return -ENOMEM; |
| |
| #ifdef __ia64__ |
| return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL); |
| #else |
| return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL); |
| #endif |
| } |
| |
| static int get_userns_fd_cb(void *data) |
| { |
| return kill(getpid(), SIGSTOP); |
| } |
| |
| static int wait_for_pid(pid_t pid) |
| { |
| int status, ret; |
| |
| again: |
| ret = waitpid(pid, &status, 0); |
| if (ret == -1) { |
| if (errno == EINTR) |
| goto again; |
| |
| return -1; |
| } |
| |
| if (!WIFEXITED(status)) |
| return -1; |
| |
| return WEXITSTATUS(status); |
| } |
| |
| static int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range) |
| { |
| int ret; |
| pid_t pid; |
| char path[256]; |
| |
| pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER); |
| if (pid < 0) |
| return -errno; |
| |
| ret = map_ids(pid, nsid, hostid, range); |
| if (ret < 0) |
| return ret; |
| |
| snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); |
| ret = open(path, O_RDONLY | O_CLOEXEC); |
| kill(pid, SIGKILL); |
| wait_for_pid(pid); |
| return ret; |
| } |
| |
| /** |
| * Validate that an attached mount in our mount namespace cannot be idmapped. |
| * (The kernel enforces that the mount's mount namespace and the caller's mount |
| * namespace match.) |
| */ |
| TEST_F(mount_setattr_idmapped, attached_mount_inside_current_mount_namespace) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| /** |
| * Validate that idmapping a mount is rejected if the mount's mount namespace |
| * and our mount namespace don't match. |
| * (The kernel enforces that the mount's mount namespace and the caller's mount |
| * namespace match.) |
| */ |
| TEST_F(mount_setattr_idmapped, attached_mount_outside_current_mount_namespace) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| ASSERT_EQ(unshare(CLONE_NEWNS), 0); |
| |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, |
| sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| /** |
| * Validate that an attached mount in our mount namespace can be idmapped. |
| */ |
| TEST_F(mount_setattr_idmapped, detached_mount_inside_current_mount_namespace) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", |
| AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| /** |
| * Validate that a detached mount not in our mount namespace can be idmapped. |
| */ |
| TEST_F(mount_setattr_idmapped, detached_mount_outside_current_mount_namespace) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| ASSERT_EQ(unshare(CLONE_NEWNS), 0); |
| |
| /* Changing mount properties on a detached mount. */ |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", |
| AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| /** |
| * Validate that currently changing the idmapping of an idmapped mount fails. |
| */ |
| TEST_F(mount_setattr_idmapped, change_idmapping) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", |
| AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| |
| /* Change idmapping on a detached mount that is already idmapped. */ |
| attr.userns_fd = get_userns_fd(0, 20000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| } |
| |
| static bool expected_uid_gid(int dfd, const char *path, int flags, |
| uid_t expected_uid, gid_t expected_gid) |
| { |
| int ret; |
| struct stat st; |
| |
| ret = fstatat(dfd, path, &st, flags); |
| if (ret < 0) |
| return false; |
| |
| return st.st_uid == expected_uid && st.st_gid == expected_gid; |
| } |
| |
| TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid) |
| { |
| int open_tree_fd = -EBADF; |
| struct mount_attr attr = { |
| .attr_set = MOUNT_ATTR_IDMAP, |
| }; |
| |
| if (!mount_setattr_supported()) |
| SKIP(return, "mount_setattr syscall not supported"); |
| |
| ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0); |
| ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0); |
| |
| open_tree_fd = sys_open_tree(-EBADF, "/mnt/A", |
| AT_RECURSIVE | |
| AT_EMPTY_PATH | |
| AT_NO_AUTOMOUNT | |
| AT_SYMLINK_NOFOLLOW | |
| OPEN_TREE_CLOEXEC | |
| OPEN_TREE_CLONE); |
| ASSERT_GE(open_tree_fd, 0); |
| |
| attr.userns_fd = get_userns_fd(0, 10000, 10000); |
| ASSERT_GE(attr.userns_fd, 0); |
| ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); |
| ASSERT_EQ(close(attr.userns_fd), 0); |
| ASSERT_EQ(close(open_tree_fd), 0); |
| |
| ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0); |
| ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0); |
| ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/b", 0, 0, 0), 0); |
| 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 |