1*fa15a241SChristian Brauner /* SPDX-License-Identifier: GPL-2.0 */
2*fa15a241SChristian Brauner /*
3*fa15a241SChristian Brauner * Hold a readdir of a directory in the page fault of its buffer and see
4*fa15a241SChristian Brauner * whether a create and a lookup in that directory wait for it. For a
5*fa15a241SChristian Brauner * directory that is permanently empty they must not.
6*fa15a241SChristian Brauner */
7*fa15a241SChristian Brauner #ifndef __SELFTESTS_READDIR_HOLD_H
8*fa15a241SChristian Brauner #define __SELFTESTS_READDIR_HOLD_H
9*fa15a241SChristian Brauner
10*fa15a241SChristian Brauner #include <errno.h>
11*fa15a241SChristian Brauner #include <fcntl.h>
12*fa15a241SChristian Brauner #include <poll.h>
13*fa15a241SChristian Brauner #include <pthread.h>
14*fa15a241SChristian Brauner #include <stdbool.h>
15*fa15a241SChristian Brauner #include <stdio.h>
16*fa15a241SChristian Brauner #include <stdlib.h>
17*fa15a241SChristian Brauner #include <string.h>
18*fa15a241SChristian Brauner #include <unistd.h>
19*fa15a241SChristian Brauner #include <linux/userfaultfd.h>
20*fa15a241SChristian Brauner #include <sys/ioctl.h>
21*fa15a241SChristian Brauner #include <sys/mman.h>
22*fa15a241SChristian Brauner #include <sys/stat.h>
23*fa15a241SChristian Brauner #include <sys/syscall.h>
24*fa15a241SChristian Brauner
25*fa15a241SChristian Brauner #define HOLD_FAULT_MS 5000 /* for the reader to reach the fault */
26*fa15a241SChristian Brauner #define HOLD_QUEUE_MS 2000 /* for the create to queue up behind it */
27*fa15a241SChristian Brauner #define HOLD_LOOKUP_MS 5000 /* for the lookup to come back */
28*fa15a241SChristian Brauner
29*fa15a241SChristian Brauner struct readdir_hold {
30*fa15a241SChristian Brauner int uffd;
31*fa15a241SChristian Brauner int taskdir; /* /proc/self/task, from before any namespace change */
32*fa15a241SChristian Brauner long page_size;
33*fa15a241SChristian Brauner char *page; /* faults until released */
34*fa15a241SChristian Brauner int dfd;
35*fa15a241SChristian Brauner pid_t creator_tid;
36*fa15a241SChristian Brauner int created;
37*fa15a241SChristian Brauner int done[2]; /* the finder writes a byte when it is back */
38*fa15a241SChristian Brauner };
39*fa15a241SChristian Brauner
40*fa15a241SChristian Brauner /*
41*fa15a241SChristian Brauner * The fault happens in the kernel, so the userfaultfd needs CAP_SYS_PTRACE
42*fa15a241SChristian Brauner * in the initial user namespace or vm.unprivileged_userfaultfd. Call this
43*fa15a241SChristian Brauner * before entering a user namespace.
44*fa15a241SChristian Brauner */
readdir_hold_init(struct readdir_hold * h)45*fa15a241SChristian Brauner static inline int readdir_hold_init(struct readdir_hold *h)
46*fa15a241SChristian Brauner {
47*fa15a241SChristian Brauner struct uffdio_api api = { .api = UFFD_API };
48*fa15a241SChristian Brauner
49*fa15a241SChristian Brauner h->page = MAP_FAILED;
50*fa15a241SChristian Brauner h->page_size = sysconf(_SC_PAGESIZE);
51*fa15a241SChristian Brauner h->taskdir = open("/proc/self/task", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
52*fa15a241SChristian Brauner h->uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
53*fa15a241SChristian Brauner if (h->uffd < 0 || h->taskdir < 0 || ioctl(h->uffd, UFFDIO_API, &api)) {
54*fa15a241SChristian Brauner if (h->uffd >= 0)
55*fa15a241SChristian Brauner close(h->uffd);
56*fa15a241SChristian Brauner if (h->taskdir >= 0)
57*fa15a241SChristian Brauner close(h->taskdir);
58*fa15a241SChristian Brauner h->uffd = h->taskdir = -1;
59*fa15a241SChristian Brauner return -1;
60*fa15a241SChristian Brauner }
61*fa15a241SChristian Brauner return 0;
62*fa15a241SChristian Brauner }
63*fa15a241SChristian Brauner
readdir_hold_destroy(struct readdir_hold * h)64*fa15a241SChristian Brauner static inline void readdir_hold_destroy(struct readdir_hold *h)
65*fa15a241SChristian Brauner {
66*fa15a241SChristian Brauner if (h->uffd >= 0)
67*fa15a241SChristian Brauner close(h->uffd);
68*fa15a241SChristian Brauner if (h->taskdir >= 0)
69*fa15a241SChristian Brauner close(h->taskdir);
70*fa15a241SChristian Brauner h->uffd = h->taskdir = -1;
71*fa15a241SChristian Brauner }
72*fa15a241SChristian Brauner
readdir_hold_reader(void * arg)73*fa15a241SChristian Brauner static inline void *readdir_hold_reader(void *arg)
74*fa15a241SChristian Brauner {
75*fa15a241SChristian Brauner struct readdir_hold *h = arg;
76*fa15a241SChristian Brauner
77*fa15a241SChristian Brauner /* the first byte written to the buffer faults until released */
78*fa15a241SChristian Brauner syscall(__NR_getdents64, h->dfd, h->page, h->page_size);
79*fa15a241SChristian Brauner return NULL;
80*fa15a241SChristian Brauner }
81*fa15a241SChristian Brauner
readdir_hold_creator(void * arg)82*fa15a241SChristian Brauner static inline void *readdir_hold_creator(void *arg)
83*fa15a241SChristian Brauner {
84*fa15a241SChristian Brauner struct readdir_hold *h = arg;
85*fa15a241SChristian Brauner
86*fa15a241SChristian Brauner h->creator_tid = syscall(__NR_gettid);
87*fa15a241SChristian Brauner /* takes the directory lock exclusive before it fails */
88*fa15a241SChristian Brauner mkdirat(h->dfd, "x", 0755);
89*fa15a241SChristian Brauner __atomic_store_n(&h->created, 1, __ATOMIC_RELEASE);
90*fa15a241SChristian Brauner return NULL;
91*fa15a241SChristian Brauner }
92*fa15a241SChristian Brauner
readdir_hold_finder(void * arg)93*fa15a241SChristian Brauner static inline void *readdir_hold_finder(void *arg)
94*fa15a241SChristian Brauner {
95*fa15a241SChristian Brauner struct readdir_hold *h = arg;
96*fa15a241SChristian Brauner int fd;
97*fa15a241SChristian Brauner
98*fa15a241SChristian Brauner /* a lookup that misses the dcache takes the lock shared */
99*fa15a241SChristian Brauner fd = openat(h->dfd, "no_such_name", O_RDONLY | O_CLOEXEC);
100*fa15a241SChristian Brauner if (fd >= 0)
101*fa15a241SChristian Brauner close(fd);
102*fa15a241SChristian Brauner if (write(h->done[1], "x", 1) != 1)
103*fa15a241SChristian Brauner perror("readdir_hold: finder");
104*fa15a241SChristian Brauner return NULL;
105*fa15a241SChristian Brauner }
106*fa15a241SChristian Brauner
107*fa15a241SChristian Brauner /* the creator is back, or waits in the kernel for the lock */
readdir_hold_creator_settled(struct readdir_hold * h)108*fa15a241SChristian Brauner static inline bool readdir_hold_creator_settled(struct readdir_hold *h)
109*fa15a241SChristian Brauner {
110*fa15a241SChristian Brauner char path[32], buf[256], *p;
111*fa15a241SChristian Brauner ssize_t n;
112*fa15a241SChristian Brauner int fd;
113*fa15a241SChristian Brauner
114*fa15a241SChristian Brauner if (__atomic_load_n(&h->created, __ATOMIC_ACQUIRE))
115*fa15a241SChristian Brauner return true;
116*fa15a241SChristian Brauner if (!h->creator_tid)
117*fa15a241SChristian Brauner return false;
118*fa15a241SChristian Brauner snprintf(path, sizeof(path), "%d/stat", h->creator_tid);
119*fa15a241SChristian Brauner fd = openat(h->taskdir, path, O_RDONLY | O_CLOEXEC);
120*fa15a241SChristian Brauner if (fd < 0)
121*fa15a241SChristian Brauner return false;
122*fa15a241SChristian Brauner n = read(fd, buf, sizeof(buf) - 1);
123*fa15a241SChristian Brauner close(fd);
124*fa15a241SChristian Brauner if (n <= 0)
125*fa15a241SChristian Brauner return false;
126*fa15a241SChristian Brauner buf[n] = 0;
127*fa15a241SChristian Brauner /* "pid (comm) state ..." */
128*fa15a241SChristian Brauner p = strrchr(buf, ')');
129*fa15a241SChristian Brauner return p && p[1] == ' ' && p[2] == 'D';
130*fa15a241SChristian Brauner }
131*fa15a241SChristian Brauner
readdir_hold_faulted(struct readdir_hold * h)132*fa15a241SChristian Brauner static inline bool readdir_hold_faulted(struct readdir_hold *h)
133*fa15a241SChristian Brauner {
134*fa15a241SChristian Brauner struct pollfd pfd = { .fd = h->uffd, .events = POLLIN };
135*fa15a241SChristian Brauner struct uffd_msg msg;
136*fa15a241SChristian Brauner
137*fa15a241SChristian Brauner if (poll(&pfd, 1, HOLD_FAULT_MS) != 1)
138*fa15a241SChristian Brauner return false;
139*fa15a241SChristian Brauner if (read(h->uffd, &msg, sizeof(msg)) != sizeof(msg))
140*fa15a241SChristian Brauner return false;
141*fa15a241SChristian Brauner return msg.event == UFFD_EVENT_PAGEFAULT;
142*fa15a241SChristian Brauner }
143*fa15a241SChristian Brauner
144*fa15a241SChristian Brauner /* let the reader go on */
readdir_hold_release(struct readdir_hold * h)145*fa15a241SChristian Brauner static inline void readdir_hold_release(struct readdir_hold *h)
146*fa15a241SChristian Brauner {
147*fa15a241SChristian Brauner struct uffdio_copy cp = {
148*fa15a241SChristian Brauner .dst = (unsigned long)h->page,
149*fa15a241SChristian Brauner .len = h->page_size,
150*fa15a241SChristian Brauner };
151*fa15a241SChristian Brauner void *zero;
152*fa15a241SChristian Brauner
153*fa15a241SChristian Brauner zero = calloc(1, h->page_size);
154*fa15a241SChristian Brauner if (!zero)
155*fa15a241SChristian Brauner return;
156*fa15a241SChristian Brauner cp.src = (unsigned long)zero;
157*fa15a241SChristian Brauner if (ioctl(h->uffd, UFFDIO_COPY, &cp) && errno != EEXIST)
158*fa15a241SChristian Brauner perror("readdir_hold: UFFDIO_COPY");
159*fa15a241SChristian Brauner free(zero);
160*fa15a241SChristian Brauner }
161*fa15a241SChristian Brauner
162*fa15a241SChristian Brauner /*
163*fa15a241SChristian Brauner * A readdir of @dfd that sticks in the fault of its buffer, then a create
164*fa15a241SChristian Brauner * and a lookup in @dfd. Whether the lookup came back while the readdir was
165*fa15a241SChristian Brauner * still stuck goes to @stalled. Returns -1 when that could not be found
166*fa15a241SChristian Brauner * out.
167*fa15a241SChristian Brauner */
readdir_hold_check(struct readdir_hold * h,int dfd,bool * stalled)168*fa15a241SChristian Brauner static inline int readdir_hold_check(struct readdir_hold *h, int dfd,
169*fa15a241SChristian Brauner bool *stalled)
170*fa15a241SChristian Brauner {
171*fa15a241SChristian Brauner pthread_t reader, creator, finder;
172*fa15a241SChristian Brauner struct uffdio_register reg = {};
173*fa15a241SChristian Brauner struct pollfd pfd;
174*fa15a241SChristian Brauner int ret = -1, i;
175*fa15a241SChristian Brauner
176*fa15a241SChristian Brauner h->dfd = dfd;
177*fa15a241SChristian Brauner h->created = 0;
178*fa15a241SChristian Brauner h->creator_tid = 0;
179*fa15a241SChristian Brauner h->page = mmap(NULL, h->page_size, PROT_READ | PROT_WRITE,
180*fa15a241SChristian Brauner MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
181*fa15a241SChristian Brauner if (h->page == MAP_FAILED)
182*fa15a241SChristian Brauner return -1;
183*fa15a241SChristian Brauner reg.range.start = (unsigned long)h->page;
184*fa15a241SChristian Brauner reg.range.len = h->page_size;
185*fa15a241SChristian Brauner reg.mode = UFFDIO_REGISTER_MODE_MISSING;
186*fa15a241SChristian Brauner if (ioctl(h->uffd, UFFDIO_REGISTER, ®) || pipe2(h->done, O_CLOEXEC))
187*fa15a241SChristian Brauner goto out_page;
188*fa15a241SChristian Brauner
189*fa15a241SChristian Brauner if (pthread_create(&reader, NULL, readdir_hold_reader, h))
190*fa15a241SChristian Brauner goto out_pipe;
191*fa15a241SChristian Brauner if (!readdir_hold_faulted(h))
192*fa15a241SChristian Brauner goto out_reader;
193*fa15a241SChristian Brauner if (pthread_create(&creator, NULL, readdir_hold_creator, h))
194*fa15a241SChristian Brauner goto out_reader;
195*fa15a241SChristian Brauner for (i = 0; i < HOLD_QUEUE_MS / 10 && !readdir_hold_creator_settled(h); i++)
196*fa15a241SChristian Brauner usleep(10000);
197*fa15a241SChristian Brauner if (pthread_create(&finder, NULL, readdir_hold_finder, h))
198*fa15a241SChristian Brauner goto out_creator;
199*fa15a241SChristian Brauner
200*fa15a241SChristian Brauner pfd.fd = h->done[0];
201*fa15a241SChristian Brauner pfd.events = POLLIN;
202*fa15a241SChristian Brauner *stalled = poll(&pfd, 1, HOLD_LOOKUP_MS) != 1;
203*fa15a241SChristian Brauner ret = 0;
204*fa15a241SChristian Brauner
205*fa15a241SChristian Brauner readdir_hold_release(h);
206*fa15a241SChristian Brauner pthread_join(finder, NULL);
207*fa15a241SChristian Brauner out_creator:
208*fa15a241SChristian Brauner if (ret)
209*fa15a241SChristian Brauner readdir_hold_release(h);
210*fa15a241SChristian Brauner pthread_join(creator, NULL);
211*fa15a241SChristian Brauner out_reader:
212*fa15a241SChristian Brauner if (ret)
213*fa15a241SChristian Brauner readdir_hold_release(h);
214*fa15a241SChristian Brauner pthread_join(reader, NULL);
215*fa15a241SChristian Brauner out_pipe:
216*fa15a241SChristian Brauner close(h->done[0]);
217*fa15a241SChristian Brauner close(h->done[1]);
218*fa15a241SChristian Brauner out_page:
219*fa15a241SChristian Brauner munmap(h->page, h->page_size);
220*fa15a241SChristian Brauner h->page = MAP_FAILED;
221*fa15a241SChristian Brauner return ret;
222*fa15a241SChristian Brauner }
223*fa15a241SChristian Brauner
224*fa15a241SChristian Brauner #endif /* __SELFTESTS_READDIR_HOLD_H */
225