1 | /* |
2 | * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | // Test that /proc/$KERNEL_THREAD/fd/ is empty. |
17 | |
18 | #undef NDEBUG |
19 | #include <sys/syscall.h> |
20 | #include <assert.h> |
21 | #include <dirent.h> |
22 | #include <limits.h> |
23 | #include <stdio.h> |
24 | #include <string.h> |
25 | #include <sys/types.h> |
26 | #include <sys/stat.h> |
27 | #include <fcntl.h> |
28 | #include <unistd.h> |
29 | |
30 | #include "proc.h" |
31 | |
32 | #define PF_KHTREAD 0x00200000 |
33 | |
34 | /* |
35 | * Test for kernel threadness atomically with openat(). |
36 | * |
37 | * Return /proc/$PID/fd descriptor if process is kernel thread. |
38 | * Return -1 if a process is userspace process. |
39 | */ |
40 | static int kernel_thread_fd(unsigned int pid) |
41 | { |
42 | unsigned int flags = 0; |
43 | char buf[4096]; |
44 | int dir_fd, fd; |
45 | ssize_t rv; |
46 | |
47 | snprintf(buf, sizeof(buf), "/proc/%u" , pid); |
48 | dir_fd = open(buf, O_RDONLY|O_DIRECTORY); |
49 | if (dir_fd == -1) |
50 | return -1; |
51 | |
52 | /* |
53 | * Believe it or not, struct task_struct::flags is directly exposed |
54 | * to userspace! |
55 | */ |
56 | fd = openat(dir_fd, "stat" , O_RDONLY); |
57 | if (fd == -1) { |
58 | close(dir_fd); |
59 | return -1; |
60 | } |
61 | rv = read(fd, buf, sizeof(buf)); |
62 | close(fd); |
63 | if (0 < rv && rv <= sizeof(buf)) { |
64 | unsigned long long flags_ull; |
65 | char *p, *end; |
66 | int i; |
67 | |
68 | assert(buf[rv - 1] == '\n'); |
69 | buf[rv - 1] = '\0'; |
70 | |
71 | /* Search backwards: ->comm can contain whitespace and ')'. */ |
72 | for (i = 0; i < 43; i++) { |
73 | p = strrchr(buf, ' '); |
74 | assert(p); |
75 | *p = '\0'; |
76 | } |
77 | |
78 | p = strrchr(buf, ' '); |
79 | assert(p); |
80 | |
81 | flags_ull = xstrtoull(p: p + 1, end: &end); |
82 | assert(*end == '\0'); |
83 | assert(flags_ull == (unsigned int)flags_ull); |
84 | |
85 | flags = flags_ull; |
86 | } |
87 | |
88 | fd = -1; |
89 | if (flags & PF_KHTREAD) { |
90 | fd = openat(dir_fd, "fd" , O_RDONLY|O_DIRECTORY); |
91 | } |
92 | close(dir_fd); |
93 | return fd; |
94 | } |
95 | |
96 | static void test_readdir(int fd) |
97 | { |
98 | DIR *d; |
99 | struct dirent *de; |
100 | |
101 | d = fdopendir(fd); |
102 | assert(d); |
103 | |
104 | de = xreaddir(d); |
105 | assert(streq(de->d_name, "." )); |
106 | assert(de->d_type == DT_DIR); |
107 | |
108 | de = xreaddir(d); |
109 | assert(streq(de->d_name, ".." )); |
110 | assert(de->d_type == DT_DIR); |
111 | |
112 | de = xreaddir(d); |
113 | assert(!de); |
114 | } |
115 | |
116 | static inline int sys_statx(int dirfd, const char *pathname, int flags, |
117 | unsigned int mask, void *stx) |
118 | { |
119 | return syscall(SYS_statx, dirfd, pathname, flags, mask, stx); |
120 | } |
121 | |
122 | static void test_lookup_fail(int fd, const char *pathname) |
123 | { |
124 | char stx[256] __attribute__((aligned(8))); |
125 | int rv; |
126 | |
127 | rv = sys_statx(dirfd: fd, pathname, flags: AT_SYMLINK_NOFOLLOW, mask: 0, stx: (void *)stx); |
128 | assert(rv == -1 && errno == ENOENT); |
129 | } |
130 | |
131 | static void test_lookup(int fd) |
132 | { |
133 | char buf[64]; |
134 | unsigned int u; |
135 | int i; |
136 | |
137 | for (i = INT_MIN; i < INT_MIN + 1024; i++) { |
138 | snprintf(buf, sizeof(buf), "%d" , i); |
139 | test_lookup_fail(fd, pathname: buf); |
140 | } |
141 | for (i = -1024; i < 1024; i++) { |
142 | snprintf(buf, sizeof(buf), "%d" , i); |
143 | test_lookup_fail(fd, pathname: buf); |
144 | } |
145 | for (u = INT_MAX - 1024; u < (unsigned int)INT_MAX + 1024; u++) { |
146 | snprintf(buf, sizeof(buf), "%u" , u); |
147 | test_lookup_fail(fd, buf); |
148 | } |
149 | for (u = UINT_MAX - 1024; u != 0; u++) { |
150 | snprintf(buf, sizeof(buf), "%u" , u); |
151 | test_lookup_fail(fd, pathname: buf); |
152 | } |
153 | } |
154 | |
155 | int main(void) |
156 | { |
157 | unsigned int pid; |
158 | int fd; |
159 | |
160 | /* |
161 | * In theory this will loop indefinitely if kernel threads are exiled |
162 | * from /proc. |
163 | * |
164 | * Start with kthreadd. |
165 | */ |
166 | pid = 2; |
167 | while ((fd = kernel_thread_fd(pid)) == -1 && pid < 1024) { |
168 | pid++; |
169 | } |
170 | /* EACCES if run as non-root. */ |
171 | if (pid >= 1024) |
172 | return 1; |
173 | |
174 | test_readdir(fd); |
175 | test_lookup(fd); |
176 | |
177 | return 0; |
178 | } |
179 | |