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 static bool
check_pf_module_available(void)69 check_pf_module_available(void)
70 {
71 int modid;
72 struct module_stat stat;
73
74 if ((modid = modfind("pf")) < 0) {
75 warn("pf module not found");
76 return false;
77 }
78 stat.version = sizeof(struct module_stat);
79 if (modstat(modid, &stat) < 0) {
80 warn("can't stat pf module id %d", modid);
81 return false;
82 }
83 return (true);
84 }
85
86 extern char **environ;
87
88 static struct sbuf *
read_fd(int fd,size_t sizehint)89 read_fd(int fd, size_t sizehint)
90 {
91 struct sbuf *sb;
92 ssize_t count;
93 char buffer[MAXBSIZE];
94
95 sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND);
96 errno = 0;
97 while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
98 sbuf_bcat(sb, buffer, count);
99 }
100 ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF");
101 sbuf_finish(sb); /* Ensure NULL-termination */
102 return (sb);
103 }
104
105 static struct sbuf *
read_file(const char * filename)106 read_file(const char *filename)
107 {
108 struct stat s;
109 struct sbuf *result;
110 int fd;
111
112 errno = 0;
113 ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename);
114 fd = open(filename, O_RDONLY);
115 ATF_REQUIRE_ERRNO(0, fd > 0);
116 result = read_fd(fd, s.st_size);
117 ATF_REQUIRE_ERRNO(0, close(fd) == 0);
118 return (result);
119 }
120
121 static void
run_command_pipe(const char * argv[],struct sbuf ** output)122 run_command_pipe(const char *argv[], struct sbuf **output)
123 {
124 posix_spawn_file_actions_t action;
125 pid_t pid;
126 int pipefds[2];
127 int status;
128
129 ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0);
130
131 posix_spawn_file_actions_init(&action);
132 posix_spawn_file_actions_addclose(&action, STDIN_FILENO);
133 posix_spawn_file_actions_addclose(&action, pipefds[1]);
134 posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO);
135 posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO);
136
137 printf("Running ");
138 for (int i=0; argv[i] != NULL; i++)
139 printf("%s ", argv[i]);
140 printf("\n");
141
142 status = posix_spawnp(
143 &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
144 ATF_REQUIRE_EQ_MSG(
145 status, 0, "posix_spawn failed: %s", strerror(errno));
146 posix_spawn_file_actions_destroy(&action);
147 close(pipefds[0]);
148
149 (*output) = read_fd(pipefds[1], 0);
150 printf("---\n%s---\n", sbuf_data(*output));
151 ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid);
152 ATF_REQUIRE_MSG(WIFEXITED(status),
153 "%s returned non-zero! Output:\n %s", argv[0], sbuf_data(*output));
154 close(pipefds[1]);
155 }
156
157 static void
run_command(const char * argv[])158 run_command(const char *argv[])
159 {
160 posix_spawn_file_actions_t action;
161 pid_t pid;
162 int status;
163
164 posix_spawn_file_actions_init(&action);
165 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
166 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
167 posix_spawn_file_actions_addopen(&action, STDIN_FILENO, "/dev/zero", O_RDONLY, 0);
168
169 printf("Running ");
170 for (int i=0; argv[i] != NULL; i++)
171 printf("%s ", argv[i]);
172 printf("\n");
173
174 status = posix_spawnp(
175 &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
176 posix_spawn_file_actions_destroy(&action);
177 waitpid(pid, &status, 0);
178 }
179
180 static void
run_pfctl_test(const char * input_path,const char * output_path,const atf_tc_t * tc,bool test_failure)181 run_pfctl_test(const char *input_path, const char *output_path,
182 const atf_tc_t *tc, bool test_failure)
183 {
184 char input_files_path[PATH_MAX];
185 struct sbuf *expected_output;
186 struct sbuf *real_output;
187
188 if (!check_pf_module_available())
189 atf_tc_skip("pf(4) is not loaded");
190
191 /* The test inputs need to be able to use relative includes. */
192 snprintf(input_files_path, sizeof(input_files_path), "%s/files",
193 atf_tc_get_config_var(tc, "srcdir"));
194 ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0);
195 expected_output = read_file(output_path);
196
197 const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path,
198 NULL };
199 run_command_pipe(argv, &real_output);
200
201 if (test_failure) {
202 /*
203 * Error output contains additional strings like line number
204 * or "skipping rule due to errors", so use regexp to see
205 * if the expected error message is there somewhere.
206 */
207 ATF_CHECK_MATCH(sbuf_data(expected_output), sbuf_data(real_output));
208 sbuf_delete(expected_output);
209 } else {
210 ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output));
211 sbuf_delete(expected_output);
212 }
213
214 sbuf_delete(real_output);
215 }
216
217 static void
do_pf_test_iface_create(const char * number)218 do_pf_test_iface_create(const char *number)
219 {
220 struct sbuf *ifconfig_output;
221 char ifname[16] = {0};
222
223 snprintf(ifname, sizeof(ifname), "vlan%s", number);
224 const char *argv[] = { "ifconfig", ifname, "create", NULL};
225 run_command_pipe(argv, &ifconfig_output);
226 sbuf_delete(ifconfig_output);
227
228 const char *argv_inet[] = { "ifconfig", ifname, "inet", "203.0.113.5/30", NULL};
229 run_command_pipe(argv_inet, &ifconfig_output);
230 sbuf_delete(ifconfig_output);
231
232 const char *argv_inet6[] = { "ifconfig", ifname, "inet6", "2001:db8::203.0.113.5/126", NULL};
233 run_command_pipe(argv_inet6, &ifconfig_output);
234 sbuf_delete(ifconfig_output);
235
236 const char *argv_show[] = { "ifconfig", ifname, NULL};
237 run_command_pipe(argv_show, &ifconfig_output);
238 sbuf_delete(ifconfig_output);
239 }
240
241 static void
do_pf_test_iface_remove(const char * number)242 do_pf_test_iface_remove(const char *number)
243 {
244 char ifname[16] = {0};
245
246 snprintf(ifname, sizeof(ifname), "vlan%s", number);
247 const char *argv[] = { "ifconfig", ifname, "destroy", NULL};
248 run_command(argv);
249 }
250
251 static void
do_pf_test(const char * number,const atf_tc_t * tc)252 do_pf_test(const char *number, const atf_tc_t *tc)
253 {
254 char *input_path;
255 char *expected_path;
256 asprintf(&input_path, "%s/files/pf%s.in",
257 atf_tc_get_config_var(tc, "srcdir"), number);
258 asprintf(&expected_path, "%s/files/pf%s.ok",
259 atf_tc_get_config_var(tc, "srcdir"), number);
260 run_pfctl_test(input_path, expected_path, tc, false);
261 free(input_path);
262 free(expected_path);
263 }
264
265 static void
do_pf_test_fail(const char * number,const atf_tc_t * tc)266 do_pf_test_fail(const char *number, const atf_tc_t *tc)
267 {
268 char *input_path;
269 char *expected_path;
270 asprintf(&input_path, "%s/files/pf%s.in",
271 atf_tc_get_config_var(tc, "srcdir"), number);
272 asprintf(&expected_path, "%s/files/pf%s.fail",
273 atf_tc_get_config_var(tc, "srcdir"), number);
274 run_pfctl_test(input_path, expected_path, tc, true);
275 free(input_path);
276 free(expected_path);
277 }
278
279 static void
do_selfpf_test(const char * number,const atf_tc_t * tc)280 do_selfpf_test(const char *number, const atf_tc_t *tc)
281 {
282 char *expected_path;
283 asprintf(&expected_path, "%s/files/pf%s.ok",
284 atf_tc_get_config_var(tc, "srcdir"), number);
285 run_pfctl_test(expected_path, expected_path, tc, false);
286 free(expected_path);
287 }
288
289 /* Standard tests perform the normal test and then the selfpf test */
290 #define PFCTL_TEST(number, descr) \
291 ATF_TC(pf##number); \
292 ATF_TC_HEAD(pf##number, tc) \
293 { \
294 atf_tc_set_md_var(tc, "descr", descr); \
295 } \
296 ATF_TC_BODY(pf##number, tc) \
297 { \
298 do_pf_test(#number, tc); \
299 } \
300 ATF_TC(selfpf##number); \
301 ATF_TC_HEAD(selfpf##number, tc) \
302 { \
303 atf_tc_set_md_var(tc, "descr", "Self " descr); \
304 } \
305 ATF_TC_BODY(selfpf##number, tc) \
306 { \
307 do_selfpf_test(#number, tc); \
308 }
309 /* Tests for failure perform only the normal test */
310 #define PFCTL_TEST_FAIL(number, descr) \
311 ATF_TC(pf##number); \
312 ATF_TC_HEAD(pf##number, tc) \
313 { \
314 atf_tc_set_md_var(tc, "descr", descr); \
315 } \
316 ATF_TC_BODY(pf##number, tc) \
317 { \
318 do_pf_test_fail(#number, tc); \
319 }
320 /* Tests with interface perform only the normal test */
321 #define PFCTL_TEST_IFACE(number, descr) \
322 ATF_TC_WITH_CLEANUP(pf##number); \
323 ATF_TC_HEAD(pf##number, tc) \
324 { \
325 atf_tc_set_md_var(tc, "descr", descr); \
326 atf_tc_set_md_var(tc, "execenv", "jail"); \
327 atf_tc_set_md_var(tc, "execenv.jail.params", "vnet"); \
328 } \
329 ATF_TC_BODY(pf##number, tc) \
330 { \
331 do_pf_test_iface_create(#number); \
332 do_pf_test(#number, tc); \
333 } \
334 ATF_TC_CLEANUP(pf##number, tc) \
335 { \
336 do_pf_test_iface_remove(#number); \
337 }
338 #include "pfctl_test_list.inc"
339 #undef PFCTL_TEST
340 #undef PFCTL_TEST_FAIL
341 #undef PFCTL_TEST_IFACE
342
ATF_TP_ADD_TCS(tp)343 ATF_TP_ADD_TCS(tp)
344 {
345 #define PFCTL_TEST(number, descr) \
346 ATF_TP_ADD_TC(tp, pf##number); \
347 ATF_TP_ADD_TC(tp, selfpf##number);
348 #define PFCTL_TEST_FAIL(number, descr) \
349 ATF_TP_ADD_TC(tp, pf##number);
350 #define PFCTL_TEST_IFACE(number, descr) \
351 ATF_TP_ADD_TC(tp, pf##number);
352 #include "pfctl_test_list.inc"
353 #undef PFCTL_TEST
354 #undef PFCTL_TEST_FAIL
355 #undef PFCTL_TEST_IFACE
356
357 return atf_no_error();
358 }
359