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
66struct freetable {
67	struct fdescenttbl *ft_table;
68	SLIST_ENTRY(freetable) ft_next;
69};
70
71struct fdescenttbl0 {
72	int	fdt_nfiles;
73	struct	filedescent fdt_ofiles[NDFILE];
74};
75
76struct 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
83static void
84openfiles(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 */
97static int
98old_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 */
121static struct kinfo_proc *
122read_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 */
137ATF_TC(free_oldtables);
138ATF_TC_HEAD(free_oldtables, tc)
139{
140	atf_tc_set_md_var(tc, "require.user", "root");
141}
142
143ATF_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
154static _Noreturn void *
155exec_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 */
165ATF_TC(oldtables_shared_via_threads);
166ATF_TC_HEAD(oldtables_shared_via_threads, tc)
167{
168	atf_tc_set_md_var(tc, "require.user", "root");
169}
170
171ATF_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 */
193static int
194filedesc_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 */
207ATF_TC(oldtables_shared_via_process);
208ATF_TC_HEAD(oldtables_shared_via_process, tc)
209{
210	atf_tc_set_md_var(tc, "require.user", "root");
211}
212
213ATF_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
261ATF_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