1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 __FBSDID("$FreeBSD$"); 30 31 #include <sys/param.h> 32 #include <sys/filedesc.h> 33 #include <sys/queue.h> 34 #include <sys/sysctl.h> 35 #include <sys/user.h> 36 #include <sys/wait.h> 37 38 #include <atf-c.h> 39 #include <fcntl.h> 40 #include <signal.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 /* linked libraries */ 47 #include <kvm.h> 48 #include <libutil.h> 49 #include <libprocstat.h> 50 #include <pthread.h> 51 52 /* test-case macro */ 53 #define AFILE "afile" 54 55 /* 56 * The following macros, struct freetable, struct fdescenttbl0 57 * and struct filedesc0 are copied from sys/kern/kern_descrip.c 58 */ 59 #define NDFILE 20 60 #define NDSLOTSIZE sizeof(NDSLOTTYPE) 61 #define NDENTRIES (NDSLOTSIZE * __CHAR_BIT) 62 #define NDSLOT(x) ((x) / NDENTRIES) 63 #define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES)) 64 #define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES) 65 66 struct freetable { 67 struct fdescenttbl *ft_table; 68 SLIST_ENTRY(freetable) ft_next; 69 }; 70 71 struct fdescenttbl0 { 72 int fdt_nfiles; 73 struct filedescent fdt_ofiles[NDFILE]; 74 }; 75 76 struct filedesc0 { 77 struct filedesc fd_fd; 78 SLIST_HEAD(, freetable) fd_free; 79 struct fdescenttbl0 fd_dfiles; 80 NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)]; 81 }; 82 83 static void 84 openfiles(int n) 85 { 86 int i, fd; 87 88 ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1); 89 close(fd); 90 for (i = 0; i < n; i++) 91 ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1); 92 } 93 94 /* 95 * Get a count of the old file descriptor tables on the freelist. 96 */ 97 static int 98 old_tables(kvm_t *kd, struct kinfo_proc *kp) 99 { 100 struct filedesc0 fdp0; 101 struct freetable *ft, tft; 102 int counter; 103 104 counter = 0; 105 106 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0); 107 108 SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) { 109 ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 ); 110 ft = &tft; 111 counter++; 112 } 113 114 return (counter); 115 } 116 117 /* 118 * The returning struct kinfo_proc stores kernel addresses that will be 119 * used by kvm_read to retrieve information for the current process. 120 */ 121 static struct kinfo_proc * 122 read_kinfo(kvm_t *kd) 123 { 124 struct kinfo_proc *kp; 125 int procs_found; 126 127 ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL); 128 ATF_REQUIRE(procs_found == 1); 129 130 return (kp); 131 } 132 133 /* 134 * Test a single threaded process that doesn't have a shared 135 * file descriptor table. The old tables should be freed. 136 */ 137 ATF_TC(free_oldtables); 138 ATF_TC_HEAD(free_oldtables, tc) 139 { 140 atf_tc_set_md_var(tc, "require.user", "root"); 141 } 142 143 ATF_TC_BODY(free_oldtables, tc) 144 { 145 kvm_t *kd; 146 struct kinfo_proc *kp; 147 148 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 149 openfiles(128); 150 kp = read_kinfo(kd); 151 ATF_CHECK(old_tables(kd,kp) == 0); 152 } 153 154 static _Noreturn void * 155 exec_thread(void *args) 156 { 157 for (;;) 158 sleep(1); 159 } 160 161 /* 162 * Test a process with two threads that doesn't have a shared file 163 * descriptor table. The old tables should not be freed. 164 */ 165 ATF_TC(oldtables_shared_via_threads); 166 ATF_TC_HEAD(oldtables_shared_via_threads, tc) 167 { 168 atf_tc_set_md_var(tc, "require.user", "root"); 169 } 170 171 ATF_TC_BODY(oldtables_shared_via_threads, tc) 172 { 173 kvm_t *kd; 174 struct kinfo_proc *kp; 175 pthread_t thread; 176 177 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 178 ATF_REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0); 179 180 openfiles(128); 181 182 kp = read_kinfo(kd); 183 ATF_CHECK(kp->ki_numthreads > 1); 184 ATF_CHECK(old_tables(kd,kp) > 1); 185 186 ATF_REQUIRE(pthread_cancel(thread) == 0); 187 ATF_REQUIRE(pthread_join(thread, NULL) == 0); 188 } 189 190 /* 191 * Get the reference count of a file descriptor table. 192 */ 193 static int 194 filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp) 195 { 196 struct filedesc fdp; 197 198 ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0); 199 200 return (fdp.fd_refcnt); 201 } 202 203 /* 204 * Test a single threaded process that shares a file descriptor 205 * table with another process. The old tables should not be freed. 206 */ 207 ATF_TC(oldtables_shared_via_process); 208 ATF_TC_HEAD(oldtables_shared_via_process, tc) 209 { 210 atf_tc_set_md_var(tc, "require.user", "root"); 211 } 212 213 ATF_TC_BODY(oldtables_shared_via_process, tc) 214 { 215 kvm_t *kd; 216 struct kinfo_proc *kp; 217 int status; 218 pid_t child, wpid; 219 220 ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL); 221 222 /* share the file descriptor table */ 223 ATF_REQUIRE((child = rfork(RFPROC)) != -1); 224 225 if (child == 0) { 226 openfiles(128); 227 raise(SIGSTOP); 228 exit(127); 229 } 230 231 /* let parent process open some files too */ 232 openfiles(128); 233 234 /* get current status of child */ 235 wpid = waitpid(child, &status, WUNTRACED); 236 237 /* child should be stopped */ 238 ATF_REQUIRE(WIFSTOPPED(status)); 239 240 /* 241 * We want to read kernel data 242 * before the child exits 243 * otherwise we'll lose a reference count 244 * to the file descriptor table 245 */ 246 if (child != 0) { 247 kp = read_kinfo(kd); 248 249 ATF_CHECK(filedesc_refcnt(kd,kp) > 1); 250 ATF_CHECK(old_tables(kd,kp) > 1); 251 252 kill(child, SIGCONT); 253 } 254 255 /* child should have exited */ 256 wpid = waitpid(child, &status, 0); 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