Skip to content

Commit aab154a

Browse files
committed
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 <[email protected]>
1 parent 2ec67bb commit aab154a

File tree

3 files changed

+196
-1
lines changed

3 files changed

+196
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22
dnotify_test
33
devpts_pts
4+
file_stressor
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-License-Identifier: GPL-2.0
22

33
CFLAGS += $(KHDR_INCLUDES)
4-
TEST_GEN_PROGS := devpts_pts
4+
TEST_GEN_PROGS := devpts_pts file_stressor
55
TEST_GEN_PROGS_EXTENDED := dnotify_test
66

77
include ../lib.mk
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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

0 commit comments

Comments
 (0)