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