1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 4 #include <errno.h> 5 #include <fcntl.h> 6 #include <limits.h> 7 #include <sched.h> 8 #include <stdio.h> 9 #include <unistd.h> 10 #include <sys/fsuid.h> 11 #include <sys/stat.h> 12 #include <sys/syscall.h> 13 14 #include <linux/mount.h> 15 #include <linux/types.h> 16 17 #include "kselftest_harness.h" 18 #include "wrappers.h" 19 #include "utils.h" 20 21 /* 22 * The test mount maps caller-visible ids [0, MAP_RANGE) onto the on-disk range 23 * [MAP_HOST, MAP_HOST + MAP_RANGE). An id outside [0, MAP_RANGE) therefore has 24 * no mapping in the mount and is not representable in the filesystem. 25 */ 26 #define MAP_HOST 10000 27 #define MAP_RANGE 10000 28 #define UNMAPPED 50000 29 30 #ifndef MOUNT_ATTR_IDMAP 31 #define MOUNT_ATTR_IDMAP 0x00100000 32 #endif 33 34 #ifndef __NR_mount_setattr 35 #define __NR_mount_setattr 442 36 #endif 37 38 static inline int sys_mount_setattr(int dfd, const char *path, 39 unsigned int flags, 40 struct mount_attr *attr, size_t size) 41 { 42 return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); 43 } 44 45 /* 46 * Clone @path into a detached mount idmapped so that caller-visible ids 47 * [0, MAP_RANGE) map onto the on-disk ids [MAP_HOST, MAP_HOST + MAP_RANGE). 48 * Returns the mount fd, or -1 if idmapped mounts are not available. 49 */ 50 static int idmapped_clone(const char *path) 51 { 52 struct mount_attr attr = { 53 .attr_set = MOUNT_ATTR_IDMAP, 54 }; 55 int fd_tree, userns_fd, ret; 56 57 fd_tree = sys_open_tree(AT_FDCWD, path, 58 OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 59 if (fd_tree < 0) 60 return -1; 61 62 userns_fd = get_userns_fd(MAP_HOST, 0, MAP_RANGE); 63 if (userns_fd < 0) { 64 close(fd_tree); 65 return -1; 66 } 67 68 attr.userns_fd = userns_fd; 69 ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, sizeof(attr)); 70 close(userns_fd); 71 if (ret) { 72 close(fd_tree); 73 return -1; 74 } 75 76 return fd_tree; 77 } 78 79 FIXTURE(idmapped_tmpfile) { 80 char dir[64]; /* non-idmapped path to the layer directory */ 81 }; 82 83 FIXTURE_SETUP(idmapped_tmpfile) 84 { 85 /* Private mount namespace so test mounts need no cleanup. */ 86 ASSERT_EQ(unshare(CLONE_NEWNS), 0); 87 ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); 88 ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); 89 90 snprintf(self->dir, sizeof(self->dir), "/tmp/d"); 91 ASSERT_EQ(mkdir(self->dir, 0777), 0); 92 /* World-writable so an unmapped caller still passes permission(). */ 93 ASSERT_EQ(chmod(self->dir, 0777), 0); 94 } 95 96 FIXTURE_TEARDOWN(idmapped_tmpfile) 97 { 98 } 99 100 /* 101 * A caller whose fsuid/fsgid have no mapping in the idmapped mount must not be 102 * able to create an O_TMPFILE. Without the check in vfs_tmpfile() the inode 103 * would be created owned by (uid_t)-1 and could then be linked into the 104 * namespace. 105 */ 106 TEST_F(idmapped_tmpfile, unmapped_caller_is_refused) 107 { 108 int mfd, fd; 109 110 mfd = idmapped_clone(self->dir); 111 if (mfd < 0) 112 SKIP(return, "idmapped mounts not supported"); 113 114 /* Become a caller outside the mount's [0, MAP_RANGE) range. */ 115 setfsgid(UNMAPPED); 116 setfsuid(UNMAPPED); 117 ASSERT_EQ(setfsuid(-1), UNMAPPED); 118 119 fd = openat(mfd, ".", O_TMPFILE | O_WRONLY, 0644); 120 ASSERT_LT(fd, 0); 121 EXPECT_EQ(errno, EOVERFLOW); 122 if (fd >= 0) 123 close(fd); 124 125 EXPECT_EQ(close(mfd), 0); 126 } 127 128 /* 129 * A mapped caller can create an O_TMPFILE and link it into the namespace; the 130 * ownership round-trips through the mount idmap. This is what makes refusing 131 * the unmapped case above necessary in the first place. 132 */ 133 TEST_F(idmapped_tmpfile, mapped_caller_creates_and_links) 134 { 135 char path[PATH_MAX]; 136 struct stat st; 137 int mfd, fd; 138 139 mfd = idmapped_clone(self->dir); 140 if (mfd < 0) 141 SKIP(return, "idmapped mounts not supported"); 142 143 /* Caller is uid/gid 0, which maps to MAP_HOST through the mount. */ 144 fd = openat(mfd, ".", O_TMPFILE | O_RDWR, 0600); 145 ASSERT_GE(fd, 0); 146 147 ASSERT_EQ(fstat(fd, &st), 0); 148 EXPECT_EQ(st.st_uid, 0); 149 EXPECT_EQ(st.st_gid, 0); 150 151 /* The tmpfile is linkable: splice it into the directory. */ 152 ASSERT_EQ(linkat(fd, "", mfd, "linked", AT_EMPTY_PATH), 0); 153 EXPECT_EQ(close(fd), 0); 154 155 ASSERT_EQ(fstatat(mfd, "linked", &st, 0), 0); 156 EXPECT_EQ(st.st_uid, 0); 157 EXPECT_EQ(st.st_gid, 0); 158 159 /* On the underlying, non-idmapped tmpfs it is stored as MAP_HOST. */ 160 snprintf(path, sizeof(path), "%s/linked", self->dir); 161 ASSERT_EQ(stat(path, &st), 0); 162 EXPECT_EQ(st.st_uid, MAP_HOST); 163 EXPECT_EQ(st.st_gid, MAP_HOST); 164 165 EXPECT_EQ(close(mfd), 0); 166 } 167 168 TEST_HARNESS_MAIN 169