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
63struct freetable {
64	struct fdescenttbl *ft_table;
65	SLIST_ENTRY(freetable) ft_next;
66};
67
68struct fdescenttbl0 {
69	int	fdt_nfiles;
70	struct	filedescent fdt_ofiles[NDFILE];
71};
72
73struct 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
80static void
81openfiles(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 */
94static int
95old_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 */
118static struct kinfo_proc *
119read_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 */
134ATF_TC(free_oldtables);
135ATF_TC_HEAD(free_oldtables, tc)
136{
137	atf_tc_set_md_var(tc, "require.user", "root");
138}
139
140ATF_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
151static _Noreturn void *
152exec_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 */
162ATF_TC(oldtables_shared_via_threads);
163ATF_TC_HEAD(oldtables_shared_via_threads, tc)
164{
165	atf_tc_set_md_var(tc, "require.user", "root");
166}
167
168ATF_TC_BODY(oldtables_shared_via_threads, tc)
169{
170	pid_t child;
171	kvm_t *kd;
172	struct kinfo_proc *kp;
173	pthread_t thread;
174
175	if ((child = rfork(RFPROC | RFCFDG)) > 0) {
176		pid_t wpid;
177		int status;
178
179		wpid = waitpid(child, &status, 0);
180		ATF_REQUIRE(wpid == child);
181		ATF_REQUIRE(WIFEXITED(status));
182		ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS);
183		return;
184	}
185
186#define	REQUIRE(expression)	do {					\
187		if (!(expression))					\
188			exit(EXIT_FAILURE);				\
189	} while (0)
190
191	REQUIRE(child == 0);
192
193	REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
194	REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
195
196	openfiles(128);
197
198	kp = read_kinfo(kd);
199	REQUIRE(kp->ki_numthreads > 1);
200	REQUIRE(old_tables(kd,kp) > 1);
201
202	REQUIRE(pthread_cancel(thread) == 0);
203	REQUIRE(pthread_join(thread, NULL) == 0);
204#undef REQUIRE
205
206	exit(EXIT_SUCCESS);
207}
208
209/*
210 * Get the reference count of a file descriptor table.
211 */
212static int
213filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp)
214{
215	struct filedesc fdp;
216
217	ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0);
218
219	return (fdp.fd_refcnt);
220}
221
222/*
223 * Test a single threaded process that shares a file descriptor
224 * table with another process. The old tables should not be freed.
225 */
226ATF_TC(oldtables_shared_via_process);
227ATF_TC_HEAD(oldtables_shared_via_process, tc)
228{
229	atf_tc_set_md_var(tc, "require.user", "root");
230}
231
232ATF_TC_BODY(oldtables_shared_via_process, tc)
233{
234	kvm_t *kd;
235	struct kinfo_proc *kp;
236	int status;
237	pid_t child, wpid;
238
239	ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
240
241	/* share the file descriptor table */
242	ATF_REQUIRE((child = rfork(RFPROC)) != -1);
243
244	if (child == 0) {
245		openfiles(128);
246		raise(SIGSTOP);
247		exit(127);
248	}
249
250	/* let parent process open some files too */
251	openfiles(128);
252
253	/* get current status of child */
254	wpid = waitpid(child, &status, WUNTRACED);
255	ATF_REQUIRE(wpid == child);
256
257	/* child should be stopped */
258	ATF_REQUIRE(WIFSTOPPED(status));
259
260	/*
261	 * We want to read kernel data
262	 * before the child exits
263	 * otherwise we'll lose a reference count
264	 * to the file descriptor table
265	 */
266	kp = read_kinfo(kd);
267
268	ATF_CHECK(filedesc_refcnt(kd,kp) > 1);
269	ATF_CHECK(old_tables(kd,kp) > 1);
270
271	kill(child, SIGCONT);
272
273	/* child should have exited */
274	wpid = waitpid(child, &status, 0);
275	ATF_REQUIRE(wpid == child);
276	ATF_REQUIRE(WIFEXITED(status));
277	ATF_REQUIRE(WEXITSTATUS(status) == 127);
278}
279
280ATF_TP_ADD_TCS(tp)
281{
282	ATF_TP_ADD_TC(tp, free_oldtables);
283	ATF_TP_ADD_TC(tp, oldtables_shared_via_threads);
284	ATF_TP_ADD_TC(tp, oldtables_shared_via_process);
285	return (atf_no_error());
286}
287