]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests: add file SLAB_TYPESAFE_BY_RCU recycling stressor
authorChristian Brauner <brauner@kernel.org>
Mon, 21 Oct 2024 11:54:46 +0000 (13:54 +0200)
committerChristian Brauner <brauner@kernel.org>
Wed, 30 Oct 2024 08:58:02 +0000 (09:58 +0100)
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>
tools/testing/selftests/filesystems/.gitignore
tools/testing/selftests/filesystems/Makefile
tools/testing/selftests/filesystems/file_stressor.c [new file with mode: 0644]

index f0c0ff20d6cf6bb6ea84f8874b5a2c00dc05bc73..828b66a10c63f51fca9d1622b056b00cba666ca3 100644 (file)
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 dnotify_test
 devpts_pts
+file_stressor
index c647fd6a0446ab9aae58fdab770743e35bab8099..66305fc34c60daf115dff97771e05400c73dc071 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := devpts_pts
+TEST_GEN_PROGS := devpts_pts file_stressor
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/filesystems/file_stressor.c b/tools/testing/selftests/filesystems/file_stressor.c
new file mode 100644 (file)
index 0000000..1136f93
--- /dev/null
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#define __SANE_USERSPACE_TYPES__
+
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#include <linux/types.h>
+#include <linux/mount.h>
+#include <sys/syscall.h>
+
+static inline int sys_fsopen(const char *fsname, unsigned int flags)
+{
+       return syscall(__NR_fsopen, fsname, flags);
+}
+
+static inline int sys_fsconfig(int fd, unsigned int cmd, const char *key,
+                              const char *value, int aux)
+{
+       return syscall(__NR_fsconfig, fd, cmd, key, value, aux);
+}
+
+static inline int sys_fsmount(int fd, unsigned int flags,
+                             unsigned int attr_flags)
+{
+       return syscall(__NR_fsmount, fd, flags, attr_flags);
+}
+
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
+#endif
+
+static inline int sys_move_mount(int from_dfd, const char *from_pathname,
+                                int to_dfd, const char *to_pathname,
+                                unsigned int flags)
+{
+       return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
+                      to_pathname, flags);
+}
+
+FIXTURE(file_stressor) {
+       int fd_tmpfs;
+       int nr_procs;
+       int max_fds;
+       pid_t *pids_openers;
+       pid_t *pids_getdents;
+       int *fd_proc_pid;
+};
+
+FIXTURE_SETUP(file_stressor)
+{
+       int fd_context;
+
+       ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+       ASSERT_EQ(mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0);
+       ASSERT_EQ(mkdir("/slab_typesafe_by_rcu", 0755), 0);
+
+       fd_context = sys_fsopen("tmpfs", 0);
+       ASSERT_GE(fd_context, 0);
+
+       ASSERT_EQ(sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0);
+       self->fd_tmpfs = sys_fsmount(fd_context, 0, 0);
+       ASSERT_GE(self->fd_tmpfs, 0);
+       ASSERT_EQ(close(fd_context), 0);
+
+       ASSERT_EQ(sys_move_mount(self->fd_tmpfs, "", -EBADF, "/slab_typesafe_by_rcu", MOVE_MOUNT_F_EMPTY_PATH), 0);
+
+       self->nr_procs = sysconf(_SC_NPROCESSORS_ONLN);
+       self->pids_openers = malloc(sizeof(pid_t) * self->nr_procs);
+       ASSERT_NE(self->pids_openers, NULL);
+       self->pids_getdents = malloc(sizeof(pid_t) * self->nr_procs);
+       ASSERT_NE(self->pids_getdents, NULL);
+       self->fd_proc_pid = malloc(sizeof(int) * self->nr_procs);
+       ASSERT_NE(self->fd_proc_pid, NULL);
+       self->max_fds = 500;
+}
+
+FIXTURE_TEARDOWN(file_stressor)
+{
+       for (int i = 0; i < self->nr_procs; i++) {
+               int wstatus;
+               pid_t pid;
+
+               pid = waitpid(self->pids_openers[i], &wstatus, 0);
+               ASSERT_EQ(pid, self->pids_openers[i]);
+               ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus));
+
+               pid = waitpid(self->pids_getdents[i], &wstatus, 0);
+               ASSERT_EQ(pid, self->pids_getdents[i]);
+               ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus));
+       }
+       free(self->pids_openers);
+       free(self->pids_getdents);
+       ASSERT_EQ(close(self->fd_tmpfs), 0);
+
+       umount2("/slab_typesafe_by_rcu", 0);
+       ASSERT_EQ(rmdir("/slab_typesafe_by_rcu"), 0);
+}
+
+TEST_F_TIMEOUT(file_stressor, slab_typesafe_by_rcu, 900 * 2)
+{
+       for (int i = 0; i < self->nr_procs; i++) {
+               pid_t pid_self;
+
+               self->pids_openers[i] = fork();
+               ASSERT_GE(self->pids_openers[i], 0);
+
+               if (self->pids_openers[i] != 0)
+                       continue;
+
+               self->pids_openers[i] = getpid();
+               for (;;) {
+                       for (int i = 0; i < self->max_fds; i++) {
+                               char path[PATH_MAX];
+                               int fd;
+
+                               sprintf(path, "/slab_typesafe_by_rcu/file-%d-%d", self->pids_openers[i], i);
+                               fd = open(path, O_CREAT | O_RDONLY | O_CLOEXEC, 0644);
+                               if (fd < 0)
+                                       continue;
+                       }
+
+                       close_range(3, ~0U, 0);
+               }
+
+               exit(0);
+       }
+
+       for (int i = 0; i < self->nr_procs; i++) {
+               char path[PATH_MAX];
+
+               sprintf(path, "/proc/%d/fd/", self->pids_openers[i]);
+               self->fd_proc_pid[i] = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+               ASSERT_GE(self->fd_proc_pid[i], 0);
+       }
+
+       for (int i = 0; i < self->nr_procs; i++) {
+               self->pids_getdents[i] = fork();
+               ASSERT_GE(self->pids_getdents[i], 0);
+
+               if (self->pids_getdents[i] != 0)
+                       continue;
+
+               self->pids_getdents[i] = getpid();
+               for (;;) {
+                       char ents[1024];
+                       ssize_t nr_read;
+
+                       /*
+                        * Concurrently 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
+                        * is being recycled due to SLAB_TYPEAFE_BY_RCU
+                        * concurrently. This will trigger various warnings in
+                        * the file reference counting code.
+                        */
+                       do {
+                               nr_read = syscall(SYS_getdents64, self->fd_proc_pid[i], ents, sizeof(ents));
+                       } while (nr_read >= 0);
+
+                       lseek(self->fd_proc_pid[i], 0, SEEK_SET);
+               }
+
+               exit(0);
+       }
+
+       ASSERT_EQ(clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec){ .tv_sec = 900 /* 15 min */ }, NULL), 0);
+
+       for (int i = 0; i < self->nr_procs; i++) {
+               kill(self->pids_openers[i], SIGKILL);
+               kill(self->pids_getdents[i], SIGKILL);
+       }
+}
+
+TEST_HARNESS_MAIN