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/cdefs.h> 29 #include <sys/param.h> 30 #include <sys/filedesc.h> 31 #include <sys/queue.h> 32 #include <sys/sysctl.h> 33 #include <sys/user.h> 34 #include <sys/wait.h> 35 36 #include <atf-c.h> 37 #include <fcntl.h> 38 #include <signal.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 /* linked libraries */ 45 #include <kvm.h> 46 #include <libutil.h> 47 #include <libprocstat.h> 48 #include <pthread.h> 49 50 /* test-case macro */ 51 #define AFILE "afile" 52 53 /* 54 * The following macros, struct freetable, struct fdescenttbl0 55 * and struct filedesc0 are copied from sys/kern/kern_descrip.c 56 */ 57 #define NDFILE 20 58 #define NDSLOTSIZE sizeof(NDSLOTTYPE) 59 #define NDENTRIES (NDSLOTSIZE * __CHAR_BIT) 60 #define NDSLOT(x) ((x) / NDENTRIES) 61 #define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES)) 62 #define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES) 63 64 struct freetable { 65 struct fdescenttbl *ft_table; 66 SLIST_ENTRY(freetable) ft_next; 67 }; 68 69 struct fdescenttbl0 { 70 int fdt_nfiles; 71 struct filedescent fdt_ofiles[NDFILE]; 72 }; 73 74 struct filedesc0 { 75 struct filedesc fd_fd; 76 SLIST_HEAD(, freetable) fd_free; 77 struct fdescenttbl0 fd_dfiles; 78 NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)]; 79 }; 80 81 static void 82 openfiles(int n) 83 { 84 int i, fd; 85 86 ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1); 87 close(fd); 88 for (i = 0; i < n; i++) 89 ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1); 90 } 91 92 /* 93 * Get a count of the old file descriptor tables on the freelist. 94 */ 95 static int 96 old_tables(kvm_t *kd, struct kinfo_proc *kp) 97 { 98 struct filedesc0 fdp0; 99 struct freetable *ft, tft; 100 int counter; 101 102 counter = 0; 103 104 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0); 105 106 SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) { 107 ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 ); 108 ft = &tft; 109 counter++; 110 } 111 112 return (counter); 113 } 114 115 /* 116 * The returning struct kinfo_proc stores kernel addresses that will be 117 * used by kvm_read to retrieve information for the current process. 118 */ 119 static struct kinfo_proc * 120 read_kinfo(kvm_t *kd) 121 { 122 struct kinfo_proc *kp; 123 int procs_found; 124 125 ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL); 126 ATF_REQUIRE(procs_found == 1); 127 128 return (kp); 129 } 130 131 /* 132 * Test a single threaded process that doesn't have a shared 133 * file descriptor table. The old tables should be freed. 134 */ 135 ATF_TC(free_oldtables); 136 ATF_TC_HEAD(free_oldtables, tc) 137 { 138 atf_tc_set_md_var(tc, "require.user", "root"); 139 } 140 141 ATF_TC_BODY(free_oldtables, tc) 142 { 143 kvm_t *kd; 144 struct kinfo_proc *kp; 145 146 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 147 openfiles(128); 148 kp = read_kinfo(kd); 149 ATF_CHECK(old_tables(kd,kp) == 0); 150 } 151 152 static _Noreturn void * 153 exec_thread(void *args) 154 { 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 if (child != 0) { 246 kp = read_kinfo(kd); 247 248 ATF_CHECK(filedesc_refcnt(kd,kp) > 1); 249 ATF_CHECK(old_tables(kd,kp) > 1); 250 251 kill(child, SIGCONT); 252 } 253 254 /* child should have exited */ 255 wpid = waitpid(child, &status, 0); 256 ATF_REQUIRE(wpid == child); 257 ATF_REQUIRE(WIFEXITED(status)); 258 ATF_REQUIRE(WEXITSTATUS(status) == 127); 259 } 260 261 ATF_TP_ADD_TCS(tp) 262 { 263 ATF_TP_ADD_TC(tp, free_oldtables); 264 ATF_TP_ADD_TC(tp, oldtables_shared_via_threads); 265 ATF_TP_ADD_TC(tp, oldtables_shared_via_process); 266 return (atf_no_error()); 267 } 268