1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2020 Rob Wing
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/param.h>
29 #include <sys/filedesc.h>
30 #include <sys/queue.h>
31 #include <sys/sysctl.h>
32 #include <sys/user.h>
33 #include <sys/wait.h>
34
35 #include <atf-c.h>
36 #include <fcntl.h>
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 /* linked libraries */
44 #include <kvm.h>
45 #include <libutil.h>
46 #include <libprocstat.h>
47 #include <pthread.h>
48
49 /* test-case macro */
50 #define AFILE "afile"
51
52 /*
53 * The following macros, struct freetable, struct fdescenttbl0
54 * and struct filedesc0 are copied from sys/kern/kern_descrip.c
55 */
56 #define NDFILE 20
57 #define NDSLOTSIZE sizeof(NDSLOTTYPE)
58 #define NDENTRIES (NDSLOTSIZE * __CHAR_BIT)
59 #define NDSLOT(x) ((x) / NDENTRIES)
60 #define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES))
61 #define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES)
62
63 struct freetable {
64 struct fdescenttbl *ft_table;
65 SLIST_ENTRY(freetable) ft_next;
66 };
67
68 struct fdescenttbl0 {
69 int fdt_nfiles;
70 struct filedescent fdt_ofiles[NDFILE];
71 };
72
73 struct filedesc0 {
74 struct filedesc fd_fd;
75 SLIST_HEAD(, freetable) fd_free;
76 struct fdescenttbl0 fd_dfiles;
77 NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)];
78 };
79
80 static void
openfiles(int n)81 openfiles(int n)
82 {
83 int i, fd;
84
85 ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1);
86 close(fd);
87 for (i = 0; i < n; i++)
88 ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1);
89 }
90
91 /*
92 * Get a count of the old file descriptor tables on the freelist.
93 */
94 static int
old_tables(kvm_t * kd,struct kinfo_proc * kp)95 old_tables(kvm_t *kd, struct kinfo_proc *kp)
96 {
97 struct filedesc0 fdp0;
98 struct freetable *ft, tft;
99 int counter;
100
101 counter = 0;
102
103 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0);
104
105 SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) {
106 ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 );
107 ft = &tft;
108 counter++;
109 }
110
111 return (counter);
112 }
113
114 /*
115 * The returning struct kinfo_proc stores kernel addresses that will be
116 * used by kvm_read to retrieve information for the current process.
117 */
118 static struct kinfo_proc *
read_kinfo(kvm_t * kd)119 read_kinfo(kvm_t *kd)
120 {
121 struct kinfo_proc *kp;
122 int procs_found;
123
124 ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL);
125 ATF_REQUIRE(procs_found == 1);
126
127 return (kp);
128 }
129
130 /*
131 * Test a single threaded process that doesn't have a shared
132 * file descriptor table. The old tables should be freed.
133 */
134 ATF_TC(free_oldtables);
ATF_TC_HEAD(free_oldtables,tc)135 ATF_TC_HEAD(free_oldtables, tc)
136 {
137 atf_tc_set_md_var(tc, "require.user", "root");
138 }
139
ATF_TC_BODY(free_oldtables,tc)140 ATF_TC_BODY(free_oldtables, tc)
141 {
142 kvm_t *kd;
143 struct kinfo_proc *kp;
144
145 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
146 openfiles(128);
147 kp = read_kinfo(kd);
148 ATF_CHECK(old_tables(kd,kp) == 0);
149 }
150
151 static _Noreturn void *
exec_thread(void * args)152 exec_thread(void *args)
153 {
154 for (;;)
155 sleep(1);
156 }
157
158 /*
159 * Test a process with two threads that doesn't have a shared file
160 * descriptor table. The old tables should not be freed.
161 */
162 ATF_TC(oldtables_shared_via_threads);
ATF_TC_HEAD(oldtables_shared_via_threads,tc)163 ATF_TC_HEAD(oldtables_shared_via_threads, tc)
164 {
165 atf_tc_set_md_var(tc, "require.user", "root");
166 }
167
ATF_TC_BODY(oldtables_shared_via_threads,tc)168 ATF_TC_BODY(oldtables_shared_via_threads, tc)
169 {
170 pid_t child;
171 kvm_t *kd;
172 struct kinfo_proc *kp;
173 pthread_t thread;
174
175 if ((child = rfork(RFPROC | RFCFDG)) > 0) {
176 pid_t wpid;
177 int status;
178
179 wpid = waitpid(child, &status, 0);
180 ATF_REQUIRE(wpid == child);
181 ATF_REQUIRE(WIFEXITED(status));
182 ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS);
183 return;
184 }
185
186 #define REQUIRE(expression) do { \
187 if (!(expression)) \
188 exit(EXIT_FAILURE); \
189 } while (0)
190
191 REQUIRE(child == 0);
192
193 REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
194 REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
195
196 openfiles(128);
197
198 kp = read_kinfo(kd);
199 REQUIRE(kp->ki_numthreads > 1);
200 REQUIRE(old_tables(kd,kp) > 1);
201
202 REQUIRE(pthread_cancel(thread) == 0);
203 REQUIRE(pthread_join(thread, NULL) == 0);
204 #undef REQUIRE
205
206 exit(EXIT_SUCCESS);
207 }
208
209 /*
210 * Get the reference count of a file descriptor table.
211 */
212 static int
filedesc_refcnt(kvm_t * kd,struct kinfo_proc * kp)213 filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp)
214 {
215 struct filedesc fdp;
216
217 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0);
218
219 return (fdp.fd_refcnt);
220 }
221
222 /*
223 * Test a single threaded process that shares a file descriptor
224 * table with another process. The old tables should not be freed.
225 */
226 ATF_TC(oldtables_shared_via_process);
ATF_TC_HEAD(oldtables_shared_via_process,tc)227 ATF_TC_HEAD(oldtables_shared_via_process, tc)
228 {
229 atf_tc_set_md_var(tc, "require.user", "root");
230 }
231
ATF_TC_BODY(oldtables_shared_via_process,tc)232 ATF_TC_BODY(oldtables_shared_via_process, tc)
233 {
234 kvm_t *kd;
235 struct kinfo_proc *kp;
236 int status;
237 pid_t child, wpid;
238
239 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
240
241 /* share the file descriptor table */
242 ATF_REQUIRE((child = rfork(RFPROC)) != -1);
243
244 if (child == 0) {
245 openfiles(128);
246 raise(SIGSTOP);
247 exit(127);
248 }
249
250 /* let parent process open some files too */
251 openfiles(128);
252
253 /* get current status of child */
254 wpid = waitpid(child, &status, WUNTRACED);
255 ATF_REQUIRE(wpid == child);
256
257 /* child should be stopped */
258 ATF_REQUIRE(WIFSTOPPED(status));
259
260 /*
261 * We want to read kernel data
262 * before the child exits
263 * otherwise we'll lose a reference count
264 * to the file descriptor table
265 */
266 kp = read_kinfo(kd);
267
268 ATF_CHECK(filedesc_refcnt(kd,kp) > 1);
269 ATF_CHECK(old_tables(kd,kp) > 1);
270
271 kill(child, SIGCONT);
272
273 /* child should have exited */
274 wpid = waitpid(child, &status, 0);
275 ATF_REQUIRE(wpid == child);
276 ATF_REQUIRE(WIFEXITED(status));
277 ATF_REQUIRE(WEXITSTATUS(status) == 127);
278 }
279
ATF_TP_ADD_TCS(tp)280 ATF_TP_ADD_TCS(tp)
281 {
282 ATF_TP_ADD_TC(tp, free_oldtables);
283 ATF_TP_ADD_TC(tp, oldtables_shared_via_threads);
284 ATF_TP_ADD_TC(tp, oldtables_shared_via_process);
285 return (atf_no_error());
286 }
287