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 * 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 * 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 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 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 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 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 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 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 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 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 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