1 | /* |
2 | * Copyright (c) 2007-2021 Apple Inc. All rights reserved. |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
5 | * |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License |
8 | * Version 2.0 (the 'License'). You may not use this file except in |
9 | * compliance with the License. The rights granted to you under the License |
10 | * may not be used to create, or enable the creation or redistribution of, |
11 | * unlawful or unlicensed copies of an Apple operating system, or to |
12 | * circumvent, violate, or enable the circumvention or violation of, any |
13 | * terms of an Apple operating system software license agreement. |
14 | * |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
17 | * |
18 | * The Original Code and all software distributed under the License are |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and |
24 | * limitations under the License. |
25 | * |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ |
28 | |
29 | /* $apfw: pf_ruleset.c,v 1.2 2007/08/10 03:00:16 jhw Exp $ */ |
30 | /* $OpenBSD: pf_ruleset.c,v 1.1 2006/10/27 13:56:51 mcbride Exp $ */ |
31 | |
32 | /* |
33 | * Copyright (c) 2001 Daniel Hartmeier |
34 | * Copyright (c) 2002,2003 Henning Brauer |
35 | * NAT64 - Copyright (c) 2010 Viagenie Inc. (http://www.viagenie.ca) |
36 | * All rights reserved. |
37 | * |
38 | * Redistribution and use in source and binary forms, with or without |
39 | * modification, are permitted provided that the following conditions |
40 | * are met: |
41 | * |
42 | * - Redistributions of source code must retain the above copyright |
43 | * notice, this list of conditions and the following disclaimer. |
44 | * - Redistributions in binary form must reproduce the above |
45 | * copyright notice, this list of conditions and the following |
46 | * disclaimer in the documentation and/or other materials provided |
47 | * with the distribution. |
48 | * |
49 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
50 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
51 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
52 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
53 | * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
54 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
55 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
56 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
57 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
58 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
59 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
60 | * POSSIBILITY OF SUCH DAMAGE. |
61 | * |
62 | * Effort sponsored in part by the Defense Advanced Research Projects |
63 | * Agency (DARPA) and Air Force Research Laboratory, Air Force |
64 | * Materiel Command, USAF, under agreement number F30602-01-2-0537. |
65 | * |
66 | */ |
67 | |
68 | #include <sys/param.h> |
69 | #include <sys/socket.h> |
70 | #ifdef KERNEL |
71 | #include <sys/systm.h> |
72 | #include <sys/malloc.h> |
73 | #include <libkern/libkern.h> |
74 | #endif /* KERNEL */ |
75 | #include <sys/mbuf.h> |
76 | |
77 | #include <netinet/ip_dummynet.h> |
78 | #include <netinet/in.h> |
79 | #include <netinet/in_systm.h> |
80 | #include <netinet/ip.h> |
81 | #include <netinet/tcp.h> |
82 | |
83 | #include <net/if.h> |
84 | #include <net/pfvar.h> |
85 | |
86 | #include <netinet/ip6.h> |
87 | |
88 | |
89 | #ifdef KERNEL |
90 | #define DPFPRINTF(format, x ...) \ |
91 | if (pf_status.debug >= PF_DEBUG_NOISY) \ |
92 | printf(format, ##x) |
93 | #define rs_malloc_data(size) kalloc_data(size, Z_WAITOK) |
94 | #define rs_malloc_type(type) kalloc_type(type, Z_WAITOK | Z_ZERO) |
95 | #define rs_free_data kfree_data |
96 | #define rs_free_type kfree_type |
97 | |
98 | #else |
99 | /* Userland equivalents so we can lend code to pfctl et al. */ |
100 | |
101 | #include <arpa/inet.h> |
102 | #include <errno.h> |
103 | #include <stdio.h> |
104 | #include <stdlib.h> |
105 | #include <string.h> |
106 | static __inline void* |
107 | rs_malloc_data(size_t size) |
108 | { |
109 | void* result = malloc(size); |
110 | if (result != NULL) { |
111 | memset(result, 0, size); |
112 | } |
113 | return result; |
114 | } |
115 | #define rs_malloc_type(type) ((type*) rs_malloc_data(sizeof(type))) |
116 | #define rs_free_data(ptr, size) free(ptr) |
117 | #define rs_free_type(type, ptr) free(ptr) |
118 | |
119 | #ifdef PFDEBUG |
120 | #include <sys/stdarg.h> |
121 | #define DPFPRINTF(format, x...) fprintf(stderr, format, ##x) |
122 | #else |
123 | #define DPFPRINTF(format, x...) ((void)0) |
124 | #endif /* PFDEBUG */ |
125 | #endif /* KERNEL */ |
126 | |
127 | |
128 | struct pf_anchor_global pf_anchors; |
129 | struct pf_anchor pf_main_anchor; |
130 | |
131 | static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *); |
132 | |
133 | RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare); |
134 | RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare); |
135 | |
136 | static __inline int |
137 | pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b) |
138 | { |
139 | int c = strcmp(s1: a->path, s2: b->path); |
140 | |
141 | return c ? (c < 0 ? -1 : 1) : 0; |
142 | } |
143 | |
144 | int |
145 | pf_get_ruleset_number(u_int8_t action) |
146 | { |
147 | switch (action) { |
148 | case PF_SCRUB: |
149 | case PF_NOSCRUB: |
150 | return PF_RULESET_SCRUB; |
151 | case PF_PASS: |
152 | case PF_DROP: |
153 | return PF_RULESET_FILTER; |
154 | case PF_NAT: |
155 | case PF_NONAT: |
156 | return PF_RULESET_NAT; |
157 | case PF_BINAT: |
158 | case PF_NOBINAT: |
159 | return PF_RULESET_BINAT; |
160 | case PF_RDR: |
161 | case PF_NORDR: |
162 | case PF_NAT64: |
163 | case PF_NONAT64: |
164 | return PF_RULESET_RDR; |
165 | #if DUMMYNET |
166 | case PF_DUMMYNET: |
167 | case PF_NODUMMYNET: |
168 | return PF_RULESET_DUMMYNET; |
169 | #endif /* DUMMYNET */ |
170 | default: |
171 | return PF_RULESET_MAX; |
172 | } |
173 | } |
174 | |
175 | void |
176 | pf_init_ruleset(struct pf_ruleset *ruleset) |
177 | { |
178 | int i; |
179 | |
180 | memset(s: ruleset, c: 0, n: sizeof(struct pf_ruleset)); |
181 | for (i = 0; i < PF_RULESET_MAX; i++) { |
182 | TAILQ_INIT(&ruleset->rules[i].queues[0]); |
183 | TAILQ_INIT(&ruleset->rules[i].queues[1]); |
184 | ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0]; |
185 | ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1]; |
186 | } |
187 | } |
188 | |
189 | struct pf_anchor * |
190 | pf_find_anchor(const char *path) |
191 | { |
192 | struct pf_anchor *key, *found; |
193 | |
194 | key = rs_malloc_type(struct pf_anchor); |
195 | strlcpy(dst: key->path, src: path, n: sizeof(key->path)); |
196 | found = RB_FIND(pf_anchor_global, &pf_anchors, key); |
197 | rs_free_type(struct pf_anchor, key); |
198 | |
199 | if (found) { |
200 | pf_reference_anchor(a: found); |
201 | } |
202 | return found; |
203 | } |
204 | |
205 | int |
206 | pf_reference_anchor(struct pf_anchor *a) |
207 | { |
208 | ASSERT(a->refcnt >= 0); |
209 | LCK_MTX_ASSERT(&pf_lock, LCK_MTX_ASSERT_OWNED); |
210 | return ++a->refcnt; |
211 | } |
212 | |
213 | int |
214 | pf_release_anchor(struct pf_anchor *a) |
215 | { |
216 | ASSERT(a->refcnt > 0); |
217 | LCK_MTX_ASSERT(&pf_lock, LCK_MTX_ASSERT_OWNED); |
218 | int r = --a->refcnt; |
219 | if (r == 0) { |
220 | pf_remove_if_empty_ruleset(&a->ruleset); |
221 | } |
222 | return r; |
223 | } |
224 | |
225 | struct pf_ruleset * |
226 | pf_find_ruleset(const char *path) |
227 | { |
228 | struct pf_anchor *anchor; |
229 | |
230 | while (*path == '/') { |
231 | path++; |
232 | } |
233 | if (!*path) { |
234 | return &pf_main_ruleset; |
235 | } |
236 | anchor = pf_find_anchor(path); |
237 | if (anchor == NULL) { |
238 | return NULL; |
239 | } else { |
240 | return &anchor->ruleset; |
241 | } |
242 | } |
243 | |
244 | struct pf_ruleset * |
245 | pf_find_ruleset_with_owner(const char *path, const char *owner, int is_anchor, |
246 | int *error) |
247 | { |
248 | struct pf_anchor *anchor; |
249 | |
250 | while (*path == '/') { |
251 | path++; |
252 | } |
253 | if (!*path) { |
254 | return &pf_main_ruleset; |
255 | } |
256 | anchor = pf_find_anchor(path); |
257 | if (anchor == NULL) { |
258 | *error = EINVAL; |
259 | return NULL; |
260 | } else { |
261 | if ((owner && (!strcmp(s1: owner, s2: anchor->owner))) |
262 | || (is_anchor && !strcmp(s1: anchor->owner, s2: "" ))) { |
263 | return &anchor->ruleset; |
264 | } |
265 | pf_release_anchor(a: anchor); |
266 | anchor = NULL; |
267 | *error = EPERM; |
268 | return NULL; |
269 | } |
270 | } |
271 | |
272 | int |
273 | pf_release_ruleset(struct pf_ruleset *r) |
274 | { |
275 | if (r->anchor == NULL) { |
276 | return 0; |
277 | } |
278 | return pf_release_anchor(a: r->anchor); |
279 | } |
280 | |
281 | struct pf_ruleset * |
282 | pf_find_or_create_ruleset(const char *path) |
283 | { |
284 | char *p, *q = NULL, *r; |
285 | struct pf_ruleset *ruleset; |
286 | struct pf_anchor *anchor = 0, *dup, *parent = NULL; |
287 | |
288 | if (path[0] == 0) { |
289 | return &pf_main_ruleset; |
290 | } |
291 | while (*path == '/') { |
292 | path++; |
293 | } |
294 | ruleset = pf_find_ruleset(path); |
295 | if (ruleset != NULL) { |
296 | return ruleset; |
297 | } |
298 | p = (char *)rs_malloc_data(MAXPATHLEN); |
299 | strlcpy(dst: p, src: path, MAXPATHLEN); |
300 | while (parent == NULL && (q = strrchr(s: p, c: '/')) != NULL) { |
301 | *q = 0; |
302 | if ((ruleset = pf_find_ruleset(path: p)) != NULL) { |
303 | parent = ruleset->anchor; |
304 | break; |
305 | } |
306 | } |
307 | if (q == NULL) { |
308 | q = p; |
309 | } else { |
310 | q++; |
311 | } |
312 | strlcpy(dst: p, src: path, MAXPATHLEN); |
313 | if (!*q) { |
314 | rs_free_data(p, MAXPATHLEN); |
315 | return NULL; |
316 | } |
317 | while ((r = strchr(s: q, c: '/')) != NULL || *q) { |
318 | if (r != NULL) { |
319 | *r = 0; |
320 | } |
321 | if (!*q || strlen(s: q) >= PF_ANCHOR_NAME_SIZE || |
322 | (parent != NULL && strlen(s: parent->path) >= |
323 | MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { |
324 | rs_free_data(p, MAXPATHLEN); |
325 | return NULL; |
326 | } |
327 | anchor = rs_malloc_type(struct pf_anchor); |
328 | if (anchor == NULL) { |
329 | rs_free_data(p, MAXPATHLEN); |
330 | return NULL; |
331 | } |
332 | RB_INIT(&anchor->children); |
333 | strlcpy(dst: anchor->name, src: q, n: sizeof(anchor->name)); |
334 | if (parent != NULL) { |
335 | strlcpy(dst: anchor->path, src: parent->path, |
336 | n: sizeof(anchor->path)); |
337 | strlcat(dst: anchor->path, src: "/" , n: sizeof(anchor->path)); |
338 | } |
339 | strlcat(dst: anchor->path, src: anchor->name, n: sizeof(anchor->path)); |
340 | if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != |
341 | NULL) { |
342 | printf("pf_find_or_create_ruleset: RB_INSERT1 " |
343 | "'%s' '%s' collides with '%s' '%s'\n" , |
344 | anchor->path, anchor->name, dup->path, dup->name); |
345 | rs_free_type(struct pf_anchor, anchor); |
346 | rs_free_data(p, MAXPATHLEN); |
347 | return NULL; |
348 | } |
349 | if (parent != NULL) { |
350 | /* reference to parent was already taken by pf_find_anchor() */ |
351 | anchor->parent = parent; |
352 | if ((dup = RB_INSERT(pf_anchor_node, &parent->children, |
353 | anchor)) != NULL) { |
354 | printf("pf_find_or_create_ruleset: " |
355 | "RB_INSERT2 '%s' '%s' collides with " |
356 | "'%s' '%s'\n" , anchor->path, anchor->name, |
357 | dup->path, dup->name); |
358 | RB_REMOVE(pf_anchor_global, &pf_anchors, |
359 | anchor); |
360 | rs_free_type(struct pf_anchor, anchor); |
361 | rs_free_data(p, MAXPATHLEN); |
362 | return NULL; |
363 | } |
364 | } |
365 | pf_init_ruleset(ruleset: &anchor->ruleset); |
366 | anchor->ruleset.anchor = anchor; |
367 | pf_reference_anchor(a: anchor); |
368 | parent = anchor; |
369 | if (r != NULL) { |
370 | q = r + 1; |
371 | } else { |
372 | *q = 0; |
373 | } |
374 | #if DUMMYNET |
375 | if (strncmp(s1: "com.apple.nlc" , s2: anchor->name, |
376 | n: sizeof("com.apple.nlc" )) == 0) { |
377 | is_nlc_enabled_glb = TRUE; |
378 | } |
379 | #endif |
380 | } |
381 | rs_free_data(p, MAXPATHLEN); |
382 | return anchor ? &anchor->ruleset : 0; |
383 | } |
384 | |
385 | void |
386 | pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset) |
387 | { |
388 | struct pf_anchor *parent; |
389 | int i; |
390 | |
391 | if (ruleset == NULL) { |
392 | return; |
393 | } |
394 | /* the main ruleset and anchor even if empty */ |
395 | if (ruleset == &pf_main_ruleset) { |
396 | return; |
397 | } |
398 | /* Each rule, child anchor, and table must take a ref count on the anchor */ |
399 | if (ruleset->anchor == NULL || ruleset->anchor->refcnt > 0) { |
400 | return; |
401 | } |
402 | ASSERT(RB_EMPTY(&ruleset->anchor->children) && |
403 | ruleset->tables == 0); |
404 | /* if we have uncommitted change for tables, bail */ |
405 | if (ruleset->topen > 0) { |
406 | return; |
407 | } |
408 | |
409 | |
410 | if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL || |
411 | !RB_EMPTY(&ruleset->anchor->children) || |
412 | ruleset->anchor->refcnt > 0 || ruleset->tables > 0 || |
413 | ruleset->topen) { |
414 | return; |
415 | } |
416 | for (i = 0; i < PF_RULESET_MAX; ++i) { |
417 | if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) || |
418 | !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) || |
419 | ruleset->rules[i].inactive.open) { |
420 | return; |
421 | } |
422 | } |
423 | RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor); |
424 | #if DUMMYNET |
425 | if (strncmp(s1: "com.apple.nlc" , s2: ruleset->anchor->name, |
426 | n: sizeof("com.apple.nlc" )) == 0) { |
427 | struct dummynet_event dn_event; |
428 | bzero(s: &dn_event, n: sizeof(dn_event)); |
429 | dn_event.dn_event_code = DUMMYNET_NLC_DISABLED; |
430 | dummynet_event_enqueue_nwk_wq_entry(&dn_event); |
431 | is_nlc_enabled_glb = FALSE; |
432 | } |
433 | #endif |
434 | if ((parent = ruleset->anchor->parent) != NULL) { |
435 | RB_REMOVE(pf_anchor_node, &parent->children, |
436 | ruleset->anchor); |
437 | } |
438 | rs_free_type(struct pf_anchor, ruleset->anchor); |
439 | if (parent == NULL) { |
440 | return; |
441 | } |
442 | pf_release_anchor(a: parent); |
443 | } |
444 | |
445 | int |
446 | pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s, |
447 | const char *name) |
448 | { |
449 | char *p, *path; |
450 | struct pf_ruleset *ruleset; |
451 | |
452 | r->anchor = NULL; |
453 | r->anchor_relative = 0; |
454 | r->anchor_wildcard = 0; |
455 | if (!name[0]) { |
456 | return 0; |
457 | } |
458 | path = (char *)rs_malloc_data(MAXPATHLEN); |
459 | if (name[0] == '/') { |
460 | strlcpy(dst: path, src: name + 1, MAXPATHLEN); |
461 | } else { |
462 | /* relative path */ |
463 | r->anchor_relative = 1; |
464 | if (s->anchor == NULL || !s->anchor->path[0]) { |
465 | path[0] = 0; |
466 | } else { |
467 | strlcpy(dst: path, src: s->anchor->path, MAXPATHLEN); |
468 | } |
469 | while (name[0] == '.' && name[1] == '.' && name[2] == '/') { |
470 | if (!path[0]) { |
471 | printf("pf_anchor_setup: .. beyond root\n" ); |
472 | rs_free_data(path, MAXPATHLEN); |
473 | return 1; |
474 | } |
475 | if ((p = strrchr(s: path, c: '/')) != NULL) { |
476 | *p = 0; |
477 | } else { |
478 | path[0] = 0; |
479 | } |
480 | r->anchor_relative++; |
481 | name += 3; |
482 | } |
483 | if (path[0]) { |
484 | strlcat(dst: path, src: "/" , MAXPATHLEN); |
485 | } |
486 | strlcat(dst: path, src: name, MAXPATHLEN); |
487 | } |
488 | if ((p = strrchr(s: path, c: '/')) != NULL && strcmp(s1: p, s2: "/*" ) == 0) { |
489 | r->anchor_wildcard = 1; |
490 | *p = 0; |
491 | } |
492 | ruleset = pf_find_or_create_ruleset(path); |
493 | rs_free_data(path, MAXPATHLEN); |
494 | if (ruleset == NULL || ruleset->anchor == NULL) { |
495 | printf("pf_anchor_setup: ruleset\n" ); |
496 | return 1; |
497 | } |
498 | r->anchor = ruleset->anchor; |
499 | return 0; |
500 | } |
501 | |
502 | int |
503 | pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r, |
504 | struct pfioc_rule *pr) |
505 | { |
506 | pr->anchor_call[0] = 0; |
507 | if (r->anchor == NULL) { |
508 | return 0; |
509 | } |
510 | if (!r->anchor_relative) { |
511 | strlcpy(dst: pr->anchor_call, src: "/" , n: sizeof(pr->anchor_call)); |
512 | strlcat(dst: pr->anchor_call, src: r->anchor->path, |
513 | n: sizeof(pr->anchor_call)); |
514 | } else { |
515 | char *a, *p; |
516 | int i; |
517 | |
518 | a = (char *)rs_malloc_data(MAXPATHLEN); |
519 | if (rs->anchor == NULL) { |
520 | a[0] = 0; |
521 | } else { |
522 | strlcpy(dst: a, src: rs->anchor->path, MAXPATHLEN); |
523 | } |
524 | for (i = 1; i < r->anchor_relative; ++i) { |
525 | if ((p = strrchr(s: a, c: '/')) == NULL) { |
526 | p = a; |
527 | } |
528 | *p = 0; |
529 | strlcat(dst: pr->anchor_call, src: "../" , |
530 | n: sizeof(pr->anchor_call)); |
531 | } |
532 | if (strncmp(s1: a, s2: r->anchor->path, n: strlen(s: a))) { |
533 | printf("pf_anchor_copyout: '%s' '%s'\n" , a, |
534 | r->anchor->path); |
535 | rs_free_data(a, MAXPATHLEN); |
536 | return 1; |
537 | } |
538 | if (strlen(s: r->anchor->path) > strlen(s: a)) { |
539 | strlcat(dst: pr->anchor_call, src: r->anchor->path + (a[0] ? |
540 | strlen(s: a) + 1 : 0), n: sizeof(pr->anchor_call)); |
541 | } |
542 | rs_free_data(a, MAXPATHLEN); |
543 | } |
544 | if (r->anchor_wildcard) { |
545 | strlcat(dst: pr->anchor_call, src: pr->anchor_call[0] ? "/*" : "*" , |
546 | n: sizeof(pr->anchor_call)); |
547 | } |
548 | return 0; |
549 | } |
550 | |
551 | void |
552 | pf_anchor_remove(struct pf_rule *r) |
553 | { |
554 | if (r->anchor == NULL) { |
555 | return; |
556 | } |
557 | if (r->anchor->refcnt <= 0) { |
558 | printf("pf_anchor_remove: broken refcount\n" ); |
559 | r->anchor = NULL; |
560 | return; |
561 | } |
562 | pf_release_anchor(a: r->anchor); |
563 | r->anchor = NULL; |
564 | } |
565 | |