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 openfiles(128); 155 for (;;) 156 sleep(1); 157 } 158 159 /* 160 * Test a process with two threads that doesn't have a shared file 161 * descriptor table. The old tables should not be freed. 162 */ 163 ATF_TC(oldtables_shared_via_threads); 164 ATF_TC_HEAD(oldtables_shared_via_threads, tc) 165 { 166 atf_tc_set_md_var(tc, "require.user", "root"); 167 } 168 169 ATF_TC_BODY(oldtables_shared_via_threads, tc) 170 { 171 kvm_t *kd; 172 struct kinfo_proc *kp; 173 pthread_t thread; 174 175 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 176 ATF_REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0); 177 178 openfiles(128); 179 180 kp = read_kinfo(kd); 181 ATF_CHECK(kp->ki_numthreads > 1); 182 ATF_CHECK(old_tables(kd,kp) > 1); 183 184 ATF_REQUIRE(pthread_cancel(thread) == 0); 185 ATF_REQUIRE(pthread_join(thread, NULL) == 0); 186 } 187 188 /* 189 * Get the reference count of a file descriptor table. 190 */ 191 static int 192 filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp) 193 { 194 struct filedesc fdp; 195 196 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0); 197 198 return (fdp.fd_refcnt); 199 } 200 201 /* 202 * Test a single threaded process that shares a file descriptor 203 * table with another process. The old tables should not be freed. 204 */ 205 ATF_TC(oldtables_shared_via_process); 206 ATF_TC_HEAD(oldtables_shared_via_process, tc) 207 { 208 atf_tc_set_md_var(tc, "require.user", "root"); 209 } 210 211 ATF_TC_BODY(oldtables_shared_via_process, tc) 212 { 213 kvm_t *kd; 214 struct kinfo_proc *kp; 215 int status; 216 pid_t child, wpid; 217 218 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 219 220 /* share the file descriptor table */ 221 ATF_REQUIRE((child = rfork(RFPROC)) != -1); 222 223 if (child == 0) { 224 openfiles(128); 225 raise(SIGSTOP); 226 exit(127); 227 } 228 229 /* let parent process open some files too */ 230 openfiles(128); 231 232 /* get current status of child */ 233 wpid = waitpid(child, &status, WUNTRACED); 234 ATF_REQUIRE(wpid == child); 235 236 /* child should be stopped */ 237 ATF_REQUIRE(WIFSTOPPED(status)); 238 239 /* 240 * We want to read kernel data 241 * before the child exits 242 * otherwise we'll lose a reference count 243 * to the file descriptor table 244 */ 245 kp = read_kinfo(kd); 246 247 ATF_CHECK(filedesc_refcnt(kd,kp) > 1); 248 ATF_CHECK(old_tables(kd,kp) > 1); 249 250 kill(child, SIGCONT); 251 252 /* child should have exited */ 253 wpid = waitpid(child, &status, 0); 254 ATF_REQUIRE(wpid == child); 255 ATF_REQUIRE(WIFEXITED(status)); 256 ATF_REQUIRE(WEXITSTATUS(status) == 127); 257 } 258 259 ATF_TP_ADD_TCS(tp) 260 { 261 ATF_TP_ADD_TC(tp, free_oldtables); 262 ATF_TP_ADD_TC(tp, oldtables_shared_via_threads); 263 ATF_TP_ADD_TC(tp, oldtables_shared_via_process); 264 return (atf_no_error()); 265 } 266