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 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 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 * 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); 135 ATF_TC_HEAD(free_oldtables, tc) 136 { 137 atf_tc_set_md_var(tc, "require.user", "root"); 138 } 139 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 * 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); 163 ATF_TC_HEAD(oldtables_shared_via_threads, tc) 164 { 165 atf_tc_set_md_var(tc, "require.user", "root"); 166 } 167 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 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); 227 ATF_TC_HEAD(oldtables_shared_via_process, tc) 228 { 229 atf_tc_set_md_var(tc, "require.user", "root"); 230 } 231 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 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