1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #define __SANE_USERSPACE_TYPES__ 4 5 #include <fcntl.h> 6 #include <limits.h> 7 #include <pthread.h> 8 #include <sched.h> 9 #include <stdio.h> 10 #include <string.h> 11 #include <sys/stat.h> 12 #include <sys/mount.h> 13 #include <unistd.h> 14 15 #include "../kselftest_harness.h" 16 17 #include <linux/types.h> 18 #include <linux/mount.h> 19 #include <sys/syscall.h> 20 21 static inline int sys_fsopen(const char *fsname, unsigned int flags) 22 { 23 return syscall(__NR_fsopen, fsname, flags); 24 } 25 26 static inline int sys_fsconfig(int fd, unsigned int cmd, const char *key, 27 const char *value, int aux) 28 { 29 return syscall(__NR_fsconfig, fd, cmd, key, value, aux); 30 } 31 32 static inline int sys_fsmount(int fd, unsigned int flags, 33 unsigned int attr_flags) 34 { 35 return syscall(__NR_fsmount, fd, flags, attr_flags); 36 } 37 38 #ifndef MOVE_MOUNT_F_EMPTY_PATH 39 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */ 40 #endif 41 42 static inline int sys_move_mount(int from_dfd, const char *from_pathname, 43 int to_dfd, const char *to_pathname, 44 unsigned int flags) 45 { 46 return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, 47 to_pathname, flags); 48 } 49 50 FIXTURE(file_stressor) { 51 int fd_tmpfs; 52 int nr_procs; 53 int max_fds; 54 pid_t *pids_openers; 55 pid_t *pids_getdents; 56 int *fd_proc_pid; 57 }; 58 59 FIXTURE_SETUP(file_stressor) 60 { 61 int fd_context; 62 63 ASSERT_EQ(unshare(CLONE_NEWNS), 0); 64 ASSERT_EQ(mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); 65 ASSERT_EQ(mkdir("/slab_typesafe_by_rcu", 0755), 0); 66 67 fd_context = sys_fsopen("tmpfs", 0); 68 ASSERT_GE(fd_context, 0); 69 70 ASSERT_EQ(sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0); 71 self->fd_tmpfs = sys_fsmount(fd_context, 0, 0); 72 ASSERT_GE(self->fd_tmpfs, 0); 73 ASSERT_EQ(close(fd_context), 0); 74 75 ASSERT_EQ(sys_move_mount(self->fd_tmpfs, "", -EBADF, "/slab_typesafe_by_rcu", MOVE_MOUNT_F_EMPTY_PATH), 0); 76 77 self->nr_procs = sysconf(_SC_NPROCESSORS_ONLN); 78 self->pids_openers = malloc(sizeof(pid_t) * self->nr_procs); 79 ASSERT_NE(self->pids_openers, NULL); 80 self->pids_getdents = malloc(sizeof(pid_t) * self->nr_procs); 81 ASSERT_NE(self->pids_getdents, NULL); 82 self->fd_proc_pid = malloc(sizeof(int) * self->nr_procs); 83 ASSERT_NE(self->fd_proc_pid, NULL); 84 self->max_fds = 500; 85 } 86 87 FIXTURE_TEARDOWN(file_stressor) 88 { 89 for (int i = 0; i < self->nr_procs; i++) { 90 int wstatus; 91 pid_t pid; 92 93 pid = waitpid(self->pids_openers[i], &wstatus, 0); 94 ASSERT_EQ(pid, self->pids_openers[i]); 95 ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus)); 96 97 pid = waitpid(self->pids_getdents[i], &wstatus, 0); 98 ASSERT_EQ(pid, self->pids_getdents[i]); 99 ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus)); 100 } 101 free(self->pids_openers); 102 free(self->pids_getdents); 103 ASSERT_EQ(close(self->fd_tmpfs), 0); 104 105 umount2("/slab_typesafe_by_rcu", 0); 106 ASSERT_EQ(rmdir("/slab_typesafe_by_rcu"), 0); 107 } 108 109 TEST_F_TIMEOUT(file_stressor, slab_typesafe_by_rcu, 900 * 2) 110 { 111 for (int i = 0; i < self->nr_procs; i++) { 112 pid_t pid_self; 113 114 self->pids_openers[i] = fork(); 115 ASSERT_GE(self->pids_openers[i], 0); 116 117 if (self->pids_openers[i] != 0) 118 continue; 119 120 self->pids_openers[i] = getpid(); 121 for (;;) { 122 for (int i = 0; i < self->max_fds; i++) { 123 char path[PATH_MAX]; 124 int fd; 125 126 sprintf(path, "/slab_typesafe_by_rcu/file-%d-%d", self->pids_openers[i], i); 127 fd = open(path, O_CREAT | O_RDONLY | O_CLOEXEC, 0644); 128 if (fd < 0) 129 continue; 130 } 131 132 close_range(3, ~0U, 0); 133 } 134 135 exit(0); 136 } 137 138 for (int i = 0; i < self->nr_procs; i++) { 139 char path[PATH_MAX]; 140 141 sprintf(path, "/proc/%d/fd/", self->pids_openers[i]); 142 self->fd_proc_pid[i] = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC); 143 ASSERT_GE(self->fd_proc_pid[i], 0); 144 } 145 146 for (int i = 0; i < self->nr_procs; i++) { 147 self->pids_getdents[i] = fork(); 148 ASSERT_GE(self->pids_getdents[i], 0); 149 150 if (self->pids_getdents[i] != 0) 151 continue; 152 153 self->pids_getdents[i] = getpid(); 154 for (;;) { 155 char ents[1024]; 156 ssize_t nr_read; 157 158 /* 159 * Concurrently read /proc/<pid>/fd/ which rougly does: 160 * 161 * f = fget_task_next(p, &fd); 162 * if (!f) 163 * break; 164 * data.mode = f->f_mode; 165 * fput(f); 166 * 167 * Which means that it'll try to get a reference to a 168 * file in another task's file descriptor table. 169 * 170 * Under heavy file load it is increasingly likely that 171 * the other task will manage to close @file and @file 172 * is being recycled due to SLAB_TYPEAFE_BY_RCU 173 * concurrently. This will trigger various warnings in 174 * the file reference counting code. 175 */ 176 do { 177 nr_read = syscall(SYS_getdents64, self->fd_proc_pid[i], ents, sizeof(ents)); 178 } while (nr_read >= 0); 179 180 lseek(self->fd_proc_pid[i], 0, SEEK_SET); 181 } 182 183 exit(0); 184 } 185 186 ASSERT_EQ(clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec){ .tv_sec = 900 /* 15 min */ }, NULL), 0); 187 188 for (int i = 0; i < self->nr_procs; i++) { 189 kill(self->pids_openers[i], SIGKILL); 190 kill(self->pids_getdents[i], SIGKILL); 191 } 192 } 193 194 TEST_HARNESS_MAIN 195