xref: /freebsd/sbin/pfctl/tests/pfctl_test.c (revision 68fe0d9cc03bd80f63a5317a633d2426ae286316)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org>
5  *
6  * This software was developed by SRI International and the University of
7  * Cambridge Computer Laboratory (Department of Computer Science and
8  * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
9  * DARPA SSITH research programme.
10  *
11  * This work was supported by Innovate UK project 105694, "Digital Security by
12  * Design (DSbD) Technology Platform Prototype".
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions are met:
16  * 1. Redistributions of source code must retain the above copyright notice,
17  *    this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright notice,
19  *    this list of conditions and the following disclaimer in the documentation
20  *    and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
23  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
26  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <stdbool.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <spawn.h>
43 #include <sys/module.h>
44 #include <sys/sbuf.h>
45 #include <sys/stat.h>
46 #include <sys/wait.h>
47 
48 #include <atf-c.h>
49 
50 /*
51  * Tests 0001-0999 are copied from OpenBSD's regress/sbin/pfctl.
52  * Tests 1001-1999 are ours (FreeBSD's own).
53  *
54  * pf: Run pfctl -nv on pfNNNN.in and check that the output matches pfNNNN.ok.
55  *     Copied from OpenBSD.  Main differences are some things not working
56  *     in FreeBSD:
57  *         * The action 'match'
58  *         * The command 'set reassemble'
59  *         * The 'from'/'to' options together with 'route-to'
60  *         * The option 'scrub' (it is an action in FreeBSD)
61  *         * Accepting undefined routing tables in actions (??: see pf0093.in)
62  *         * The 'route' option
63  *         * The 'set queue def' option
64  * selfpf: Feed pfctl output through pfctl again and verify it stays the same.
65  *         Copied from OpenBSD.
66  */
67 
68 extern char **environ;
69 
70 static struct sbuf *
read_fd(int fd,size_t sizehint)71 read_fd(int fd, size_t sizehint)
72 {
73 	struct sbuf *sb;
74 	ssize_t count;
75 	char buffer[MAXBSIZE];
76 
77 	sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND);
78 	errno = 0;
79 	while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
80 		sbuf_bcat(sb, buffer, count);
81 	}
82 	ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF");
83 	sbuf_finish(sb); /* Ensure NULL-termination */
84 	return (sb);
85 }
86 
87 static struct sbuf *
read_file(const char * filename)88 read_file(const char *filename)
89 {
90 	struct stat s;
91 	struct sbuf *result;
92 	int fd;
93 
94 	errno = 0;
95 	ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename);
96 	fd = open(filename, O_RDONLY);
97 	ATF_REQUIRE_ERRNO(0, fd > 0);
98 	result = read_fd(fd, s.st_size);
99 	ATF_REQUIRE_ERRNO(0, close(fd) == 0);
100 	return (result);
101 }
102 
103 static void
run_command_pipe(const char * argv[],struct sbuf ** output)104 run_command_pipe(const char *argv[], struct sbuf **output)
105 {
106 	posix_spawn_file_actions_t action;
107 	pid_t pid;
108 	int pipefds[2];
109 	int status;
110 
111 	ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0);
112 
113 	posix_spawn_file_actions_init(&action);
114 	posix_spawn_file_actions_addclose(&action, STDIN_FILENO);
115 	posix_spawn_file_actions_addclose(&action, pipefds[1]);
116 	posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO);
117 	posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO);
118 
119 	printf("Running ");
120 	for (int i=0; argv[i] != NULL; i++)
121 		printf("%s ", argv[i]);
122 	printf("\n");
123 
124 	status = posix_spawnp(
125 	    &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
126 	ATF_REQUIRE_EQ_MSG(
127 	    status, 0, "posix_spawn failed: %s", strerror(errno));
128 	posix_spawn_file_actions_destroy(&action);
129 	close(pipefds[0]);
130 
131 	(*output) = read_fd(pipefds[1], 0);
132 	printf("---\n%s---\n", sbuf_data(*output));
133 	ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid);
134 	ATF_REQUIRE_MSG(WIFEXITED(status),
135 	    "%s returned non-zero! Output:\n %s", argv[0], sbuf_data(*output));
136 	close(pipefds[1]);
137 }
138 
139 static void
run_command(const char * argv[])140 run_command(const char *argv[])
141 {
142 	posix_spawn_file_actions_t action;
143 	pid_t pid;
144 	int status;
145 
146 	posix_spawn_file_actions_init(&action);
147 	posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
148 	posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
149 	posix_spawn_file_actions_addopen(&action, STDIN_FILENO, "/dev/zero", O_RDONLY, 0);
150 
151 	printf("Running ");
152 	for (int i=0; argv[i] != NULL; i++)
153 		printf("%s ", argv[i]);
154 	printf("\n");
155 
156 	status = posix_spawnp(
157 	    &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
158 	posix_spawn_file_actions_destroy(&action);
159 	waitpid(pid, &status, 0);
160 }
161 
162 static void
run_pfctl_test(const char * input_path,const char * output_path,const atf_tc_t * tc,bool test_failure)163 run_pfctl_test(const char *input_path, const char *output_path,
164     const atf_tc_t *tc, bool test_failure)
165 {
166 	char input_files_path[PATH_MAX];
167 	struct sbuf *expected_output;
168 	struct sbuf *real_output;
169 
170 	/* The test inputs need to be able to use relative includes. */
171 	snprintf(input_files_path, sizeof(input_files_path), "%s/files",
172 	    atf_tc_get_config_var(tc, "srcdir"));
173 	ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0);
174 	expected_output = read_file(output_path);
175 
176 	const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path,
177 	    NULL };
178 	run_command_pipe(argv, &real_output);
179 
180 	if (test_failure) {
181 		/*
182 		 * Error output contains additional strings like line number
183 		 * or "skipping rule due to errors", so use regexp to see
184 		 * if the expected error message is there somewhere.
185 		 */
186 		ATF_CHECK_MATCH(sbuf_data(expected_output), sbuf_data(real_output));
187 		sbuf_delete(expected_output);
188 	} else {
189 		ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output));
190 		sbuf_delete(expected_output);
191 	}
192 
193 	sbuf_delete(real_output);
194 }
195 
196 static void
do_pf_test_iface_create(const char * number)197 do_pf_test_iface_create(const char *number)
198 {
199 	struct sbuf	*ifconfig_output;
200 	char		ifname[16] = {0};
201 
202 	snprintf(ifname, sizeof(ifname), "vlan%s", number);
203 	const char *argv[] = { "ifconfig", ifname, "create", NULL};
204 	run_command_pipe(argv, &ifconfig_output);
205 	sbuf_delete(ifconfig_output);
206 
207 	const char *argv_inet[] = { "ifconfig", ifname, "inet", "203.0.113.5/30", NULL};
208 	run_command_pipe(argv_inet, &ifconfig_output);
209 	sbuf_delete(ifconfig_output);
210 
211 	const char *argv_inet6[] = { "ifconfig", ifname, "inet6", "2001:db8::203.0.113.5/126", NULL};
212 	run_command_pipe(argv_inet6, &ifconfig_output);
213 	sbuf_delete(ifconfig_output);
214 
215 	const char *argv_show[] = { "ifconfig", ifname, NULL};
216 	run_command_pipe(argv_show, &ifconfig_output);
217 	sbuf_delete(ifconfig_output);
218 }
219 
220 static void
do_pf_test_iface_remove(const char * number)221 do_pf_test_iface_remove(const char *number)
222 {
223 	char		ifname[16] = {0};
224 
225 	snprintf(ifname, sizeof(ifname), "vlan%s", number);
226 	const char *argv[] = { "ifconfig", ifname, "destroy", NULL};
227 	run_command(argv);
228 }
229 
230 static void
do_pf_test(const char * number,const atf_tc_t * tc)231 do_pf_test(const char *number, const atf_tc_t *tc)
232 {
233 	char *input_path;
234 	char *expected_path;
235 	asprintf(&input_path, "%s/files/pf%s.in",
236 	    atf_tc_get_config_var(tc, "srcdir"), number);
237 	asprintf(&expected_path, "%s/files/pf%s.ok",
238 	    atf_tc_get_config_var(tc, "srcdir"), number);
239 	run_pfctl_test(input_path, expected_path, tc, false);
240 	free(input_path);
241 	free(expected_path);
242 }
243 
244 static void
do_pf_test_fail(const char * number,const atf_tc_t * tc)245 do_pf_test_fail(const char *number, const atf_tc_t *tc)
246 {
247 	char *input_path;
248 	char *expected_path;
249 	asprintf(&input_path, "%s/files/pf%s.in",
250 	    atf_tc_get_config_var(tc, "srcdir"), number);
251 	asprintf(&expected_path, "%s/files/pf%s.fail",
252 	    atf_tc_get_config_var(tc, "srcdir"), number);
253 	run_pfctl_test(input_path, expected_path, tc, true);
254 	free(input_path);
255 	free(expected_path);
256 }
257 
258 static void
do_selfpf_test(const char * number,const atf_tc_t * tc)259 do_selfpf_test(const char *number, const atf_tc_t *tc)
260 {
261 	char *expected_path;
262 	asprintf(&expected_path, "%s/files/pf%s.ok",
263 	    atf_tc_get_config_var(tc, "srcdir"), number);
264 	run_pfctl_test(expected_path, expected_path, tc, false);
265 	free(expected_path);
266 }
267 
268 /* Standard tests perform the normal test and then the selfpf test */
269 #define PFCTL_TEST(number, descr)				\
270 	ATF_TC(pf##number);					\
271 	ATF_TC_HEAD(pf##number, tc)				\
272 	{							\
273 		atf_tc_set_md_var(tc, "descr", descr);		\
274 		atf_tc_set_md_var(tc, "require.kmods", "pf");	\
275 	}							\
276 	ATF_TC_BODY(pf##number, tc)				\
277 	{							\
278 		do_pf_test(#number, tc);			\
279 	}							\
280 	ATF_TC(selfpf##number);					\
281 	ATF_TC_HEAD(selfpf##number, tc)				\
282 	{							\
283 		atf_tc_set_md_var(tc, "descr", "Self " descr);	\
284 		atf_tc_set_md_var(tc, "require.kmods", "pf");	\
285 	}							\
286 	ATF_TC_BODY(selfpf##number, tc)				\
287 	{							\
288 		do_selfpf_test(#number, tc);			\
289 	}
290 /* Tests for failure perform only the normal test */
291 #define PFCTL_TEST_FAIL(number, descr)				\
292 	ATF_TC(pf##number);					\
293 	ATF_TC_HEAD(pf##number, tc)				\
294 	{							\
295 		atf_tc_set_md_var(tc, "descr", descr);		\
296 		atf_tc_set_md_var(tc, "require.kmods", "pf");	\
297 	}							\
298 	ATF_TC_BODY(pf##number, tc)				\
299 	{							\
300 		do_pf_test_fail(#number, tc);			\
301 	}
302 /* Tests with interface perform only the normal test */
303 #define PFCTL_TEST_IFACE(number, descr)				\
304 	ATF_TC_WITH_CLEANUP(pf##number);			\
305 	ATF_TC_HEAD(pf##number, tc)				\
306 	{							\
307 		atf_tc_set_md_var(tc, "descr", descr);		\
308 		atf_tc_set_md_var(tc, "execenv", "jail");	\
309 		atf_tc_set_md_var(tc, "execenv.jail.params", "vnet");	\
310 		atf_tc_set_md_var(tc, "require.kmods", "pf");	\
311 	}							\
312 	ATF_TC_BODY(pf##number, tc)				\
313 	{							\
314 		do_pf_test_iface_create(#number);		\
315 		do_pf_test(#number, tc);			\
316 	}							\
317 	ATF_TC_CLEANUP(pf##number, tc)				\
318 	{							\
319 		do_pf_test_iface_remove(#number);		\
320 	}
321 #include "pfctl_test_list.inc"
322 #undef PFCTL_TEST
323 #undef PFCTL_TEST_FAIL
324 #undef PFCTL_TEST_IFACE
325 
ATF_TP_ADD_TCS(tp)326 ATF_TP_ADD_TCS(tp)
327 {
328 #define PFCTL_TEST(number, descr)		\
329 	ATF_TP_ADD_TC(tp, pf##number);		\
330 	ATF_TP_ADD_TC(tp, selfpf##number);
331 #define PFCTL_TEST_FAIL(number, descr)		\
332 	ATF_TP_ADD_TC(tp, pf##number);
333 #define PFCTL_TEST_IFACE(number, descr)		\
334 	ATF_TP_ADD_TC(tp, pf##number);
335 #include "pfctl_test_list.inc"
336 #undef PFCTL_TEST
337 #undef PFCTL_TEST_FAIL
338 #undef PFCTL_TEST_IFACE
339 
340 	return atf_no_error();
341 }
342