Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: add file SLAB_TYPESAFE_BY_RCU recycling stressor

Add a simple file stressor that lives directly in-tree. This will create
a bunch of processes that each open 500 file descriptors and then use
close_range() to close them all.

Concurrently, other processes read /proc/<pid>/fd/ which rougly does

f = fget_task_next(p, &fd);
if (!f)
break;
data.mode = f->f_mode;
fput(f);

Which means that it'll try to get a reference to a file in another
task's file descriptor table.

Under heavy file load it is increasingly likely that the other task will
manage to close @file and @file will be recycled due to
SLAB_TYPEAFE_BY_RCU concurrently. This will trigger various warnings in
the file reference counting code.

Link: https://lore.kernel.org/r/20241021-vergab-streuen-924df15dceb9@brauner
Signed-off-by: Christian Brauner <brauner@kernel.org>

+196 -1
+1
tools/testing/selftests/filesystems/.gitignore
··· 1 1 # SPDX-License-Identifier: GPL-2.0-only 2 2 dnotify_test 3 3 devpts_pts 4 + file_stressor
+1 -1
tools/testing/selftests/filesystems/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 3 3 CFLAGS += $(KHDR_INCLUDES) 4 - TEST_GEN_PROGS := devpts_pts 4 + TEST_GEN_PROGS := devpts_pts file_stressor 5 5 TEST_GEN_PROGS_EXTENDED := dnotify_test 6 6 7 7 include ../lib.mk
+194
tools/testing/selftests/filesystems/file_stressor.c
··· 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