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