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