xref: /openbsd/sys/net/pf_ruleset.c (revision 062cda8b)
1 /*	$OpenBSD: pf_ruleset.c,v 1.21 2023/06/30 09:58:30 mvs Exp $ */
2 
3 /*
4  * Copyright (c) 2001 Daniel Hartmeier
5  * Copyright (c) 2002,2003 Henning Brauer
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  *    - Redistributions of source code must retain the above copyright
13  *      notice, this list of conditions and the following disclaimer.
14  *    - Redistributions in binary form must reproduce the above
15  *      copyright notice, this list of conditions and the following
16  *      disclaimer in the documentation and/or other materials provided
17  *      with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  * Effort sponsored in part by the Defense Advanced Research Projects
33  * Agency (DARPA) and Air Force Research Laboratory, Air Force
34  * Materiel Command, USAF, under agreement number F30602-01-2-0537.
35  *
36  */
37 
38 #include <sys/param.h>
39 #include <sys/socket.h>
40 #ifdef _KERNEL
41 #include <sys/systm.h>
42 #include <sys/mbuf.h>
43 #include <sys/pool.h>
44 #endif /* _KERNEL */
45 #include <sys/syslog.h>
46 
47 #include <netinet/in.h>
48 #include <netinet/ip.h>
49 #include <netinet/tcp.h>
50 
51 #include <net/if.h>
52 #include <net/pfvar.h>
53 
54 #ifdef INET6
55 #include <netinet/ip6.h>
56 #endif /* INET6 */
57 
58 
59 #ifdef _KERNEL
60 #define rs_malloc(x)		malloc(x, M_PF, M_WAITOK|M_CANFAIL|M_ZERO)
61 #define rs_free(x, siz)		free(x, M_PF, siz)
62 #define rs_pool_get_anchor()	pool_get(&pf_anchor_pl, \
63 				    PR_WAITOK|PR_LIMITFAIL|PR_ZERO)
64 #define rs_pool_put_anchor(x)	pool_put(&pf_anchor_pl, x)
65 
66 struct pool	pf_anchor_pl;
67 
68 #else	/* !_KERNEL */
69 /* Userland equivalents so we can lend code to pfctl et al. */
70 
71 #include <arpa/inet.h>
72 #include <errno.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #define rs_malloc(x)		calloc(1, x)
77 #define rs_free(x, siz)		freezero(x, siz)
78 #define rs_pool_get_anchor()	calloc(1, sizeof(struct pf_anchor))
79 #define rs_pool_put_anchor(x)	freezero(x, sizeof(struct pf_anchor))
80 
81 #ifdef PFDEBUG
82 #include <sys/stdarg.h>	/* for DPFPRINTF() */
83 #endif	/* PFDEBUG */
84 #endif /* _KERNEL */
85 
86 
87 struct pf_anchor_global	 pf_anchors;
88 struct pf_anchor	 pf_main_anchor;
89 
90 static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *);
91 
92 RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
93 RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
94 
95 static __inline int
pf_anchor_compare(struct pf_anchor * a,struct pf_anchor * b)96 pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b)
97 {
98 	int c = strcmp(a->path, b->path);
99 
100 	return (c ? (c < 0 ? -1 : 1) : 0);
101 }
102 
103 void
pf_init_ruleset(struct pf_ruleset * ruleset)104 pf_init_ruleset(struct pf_ruleset *ruleset)
105 {
106 	memset(ruleset, 0, sizeof(struct pf_ruleset));
107 	TAILQ_INIT(&ruleset->rules.queues[0]);
108 	TAILQ_INIT(&ruleset->rules.queues[1]);
109 	ruleset->rules.active.ptr = &ruleset->rules.queues[0];
110 	ruleset->rules.inactive.ptr = &ruleset->rules.queues[1];
111 }
112 
113 struct pf_anchor *
pf_find_anchor(const char * path)114 pf_find_anchor(const char *path)
115 {
116 	struct pf_anchor	*key, *found;
117 
118 	key = rs_malloc(sizeof(*key));
119 	if (key == NULL)
120 		return (NULL);
121 	strlcpy(key->path, path, sizeof(key->path));
122 	found = RB_FIND(pf_anchor_global, &pf_anchors, key);
123 	rs_free(key, sizeof(*key));
124 	return (found);
125 }
126 
127 struct pf_ruleset *
pf_find_ruleset(const char * path)128 pf_find_ruleset(const char *path)
129 {
130 	struct pf_anchor	*anchor;
131 
132 	while (*path == '/')
133 		path++;
134 	if (!*path)
135 		return (&pf_main_ruleset);
136 	anchor = pf_find_anchor(path);
137 	if (anchor == NULL)
138 		return (NULL);
139 	else
140 		return (&anchor->ruleset);
141 }
142 
143 struct pf_ruleset *
pf_get_leaf_ruleset(char * path,char ** path_remainder)144 pf_get_leaf_ruleset(char *path, char **path_remainder)
145 {
146 	struct pf_ruleset	*ruleset;
147 	char			*leaf, *p;
148 	int			 i = 0;
149 
150 	p = path;
151 	while (*p == '/')
152 		p++;
153 
154 	ruleset = pf_find_ruleset(p);
155 	leaf = p;
156 	while (ruleset == NULL) {
157 		leaf = strrchr(p, '/');
158 		if (leaf != NULL) {
159 			*leaf = '\0';
160 			i++;
161 			ruleset = pf_find_ruleset(p);
162 		} else {
163 			leaf = path;
164 			/*
165 			 * if no path component exists, then main ruleset is
166 			 * our parent.
167 			 */
168 			ruleset = &pf_main_ruleset;
169 		}
170 	}
171 
172 	if (path_remainder != NULL)
173 		*path_remainder = leaf;
174 
175 	/* restore slashes in path.  */
176 	while (i != 0) {
177 		while (*leaf != '\0')
178 			leaf++;
179 		*leaf = '/';
180 		i--;
181 	}
182 
183 	return (ruleset);
184 }
185 
186 struct pf_anchor *
pf_create_anchor(struct pf_anchor * parent,const char * aname)187 pf_create_anchor(struct pf_anchor *parent, const char *aname)
188 {
189 	struct pf_anchor	*anchor, *dup;
190 
191 	if (!*aname || (strlen(aname) >= PF_ANCHOR_NAME_SIZE) ||
192 	    ((parent != NULL) && (strlen(parent->path) >= PF_ANCHOR_MAXPATH)))
193 		return (NULL);
194 
195 	anchor = rs_pool_get_anchor();
196 	if (anchor == NULL)
197 		return (NULL);
198 
199 	RB_INIT(&anchor->children);
200 	strlcpy(anchor->name, aname, sizeof(anchor->name));
201 	if (parent != NULL) {
202 		/*
203 		 * Make sure path for levels 2, 3, ... is terminated by '/':
204 		 *	1/2/3/...
205 		 */
206 		strlcpy(anchor->path, parent->path, sizeof(anchor->path));
207 		strlcat(anchor->path, "/", sizeof(anchor->path));
208 	}
209 	strlcat(anchor->path, anchor->name, sizeof(anchor->path));
210 
211 	if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != NULL) {
212 		DPFPRINTF(LOG_NOTICE,
213 		    "%s: RB_INSERT to global '%s' '%s' collides with '%s' '%s'",
214 		    __func__, anchor->path, anchor->name, dup->path, dup->name);
215 		rs_pool_put_anchor(anchor);
216 		return (NULL);
217 	}
218 
219 	if (parent != NULL) {
220 		anchor->parent = parent;
221 		dup = RB_INSERT(pf_anchor_node, &parent->children, anchor);
222 		if (dup != NULL) {
223 			DPFPRINTF(LOG_NOTICE,
224 			    "%s: RB_INSERT to parent '%s' '%s' collides with "
225 			    "'%s' '%s'", __func__, anchor->path, anchor->name,
226 			    dup->path, dup->name);
227 			RB_REMOVE(pf_anchor_global, &pf_anchors,
228 			    anchor);
229 			rs_pool_put_anchor(anchor);
230 			return (NULL);
231 		}
232 	}
233 
234 	pf_init_ruleset(&anchor->ruleset);
235 	anchor->ruleset.anchor = anchor;
236 #ifdef	_KERNEL
237 	refcnt_init(&anchor->ref);
238 #endif
239 
240 	return (anchor);
241 }
242 
243 struct pf_ruleset *
pf_find_or_create_ruleset(const char * path)244 pf_find_or_create_ruleset(const char *path)
245 {
246 	char			*p, *aname, *r;
247 	struct pf_ruleset	*ruleset;
248 	struct pf_anchor	*anchor;
249 
250 	if (path[0] == 0)
251 		return (&pf_main_ruleset);
252 
253 	while (*path == '/')
254 		path++;
255 
256 	ruleset = pf_find_ruleset(path);
257 	if (ruleset != NULL)
258 		return (ruleset);
259 
260 	p = rs_malloc(MAXPATHLEN);
261 	if (p == NULL)
262 		return (NULL);
263 	strlcpy(p, path, MAXPATHLEN);
264 
265 	ruleset = pf_get_leaf_ruleset(p, &aname);
266 	anchor = ruleset->anchor;
267 
268 	while (*aname == '/')
269 		aname++;
270 	/*
271 	 * aname is a path remainder, which contains nodes we must create.  We
272 	 * process the aname path from left to right, effectively descending
273 	 * from parents to children.
274 	 */
275 	while ((r = strchr(aname, '/')) != NULL || *aname) {
276 		if (r != NULL)
277 			*r = 0;
278 
279 		anchor = pf_create_anchor(anchor, aname);
280 		if (anchor == NULL) {
281 			rs_free(p, MAXPATHLEN);
282 			return (NULL);
283 		}
284 
285 		if (r == NULL)
286 			break;
287 		else
288 			aname = r + 1;
289 	}
290 
291 	rs_free(p, MAXPATHLEN);
292 	return (&anchor->ruleset);
293 }
294 
295 void
pf_remove_if_empty_ruleset(struct pf_ruleset * ruleset)296 pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
297 {
298 	struct pf_anchor	*parent;
299 
300 	while (ruleset != NULL) {
301 		if (ruleset == &pf_main_ruleset ||
302 		    !RB_EMPTY(&ruleset->anchor->children) ||
303 		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
304 		    ruleset->topen)
305 			return;
306 		if (!TAILQ_EMPTY(ruleset->rules.active.ptr) ||
307 		    !TAILQ_EMPTY(ruleset->rules.inactive.ptr) ||
308 		    ruleset->rules.inactive.open)
309 			return;
310 		RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor);
311 		if ((parent = ruleset->anchor->parent) != NULL)
312 			RB_REMOVE(pf_anchor_node, &parent->children,
313 			    ruleset->anchor);
314 		pf_anchor_rele(ruleset->anchor);
315 		if (parent == NULL)
316 			return;
317 		ruleset = &parent->ruleset;
318 	}
319 }
320 
321 int
pf_anchor_setup(struct pf_rule * r,const struct pf_ruleset * s,const char * name)322 pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s,
323     const char *name)
324 {
325 	char			*p, *path;
326 	struct pf_ruleset	*ruleset;
327 
328 	r->anchor = NULL;
329 	r->anchor_relative = 0;
330 	r->anchor_wildcard = 0;
331 	if (!name[0])
332 		return (0);
333 	path = rs_malloc(MAXPATHLEN);
334 	if (path == NULL)
335 		return (1);
336 	if (name[0] == '/')
337 		strlcpy(path, name + 1, MAXPATHLEN);
338 	else {
339 		/* relative path */
340 		r->anchor_relative = 1;
341 		if (s->anchor == NULL || !s->anchor->path[0])
342 			path[0] = 0;
343 		else
344 			strlcpy(path, s->anchor->path, MAXPATHLEN);
345 		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
346 			if (!path[0]) {
347 				DPFPRINTF(LOG_NOTICE,
348 				    "pf_anchor_setup: .. beyond root");
349 				rs_free(path, MAXPATHLEN);
350 				return (1);
351 			}
352 			if ((p = strrchr(path, '/')) != NULL)
353 				*p = 0;
354 			else
355 				path[0] = 0;
356 			r->anchor_relative++;
357 			name += 3;
358 		}
359 		if (path[0])
360 			strlcat(path, "/", MAXPATHLEN);
361 		strlcat(path, name, MAXPATHLEN);
362 	}
363 	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
364 		r->anchor_wildcard = 1;
365 		*p = 0;
366 	}
367 	ruleset = pf_find_or_create_ruleset(path);
368 	rs_free(path, MAXPATHLEN);
369 	if (ruleset == NULL || ruleset == &pf_main_ruleset) {
370 		DPFPRINTF(LOG_NOTICE,
371 		    "pf_anchor_setup: ruleset");
372 		return (1);
373 	}
374 	r->anchor = ruleset->anchor;
375 	r->anchor->refcnt++;
376 	return (0);
377 }
378 
379 int
pf_anchor_copyout(const struct pf_ruleset * rs,const struct pf_rule * r,struct pfioc_rule * pr)380 pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
381     struct pfioc_rule *pr)
382 {
383 	pr->anchor_call[0] = 0;
384 	if (r->anchor == NULL)
385 		return (0);
386 	if (!r->anchor_relative) {
387 		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
388 		strlcat(pr->anchor_call, r->anchor->path,
389 		    sizeof(pr->anchor_call));
390 	} else {
391 		char	*a, *p;
392 		int	 i;
393 
394 		a = rs_malloc(MAXPATHLEN);
395 		if (a == NULL)
396 			return (1);
397 		if (rs == &pf_main_ruleset)
398 			a[0] = 0;
399 		else
400 			strlcpy(a, rs->anchor->path, MAXPATHLEN);
401 		for (i = 1; i < r->anchor_relative; ++i) {
402 			if ((p = strrchr(a, '/')) == NULL)
403 				p = a;
404 			*p = 0;
405 			strlcat(pr->anchor_call, "../",
406 			    sizeof(pr->anchor_call));
407 		}
408 		if (strncmp(a, r->anchor->path, strlen(a))) {
409 			DPFPRINTF(LOG_NOTICE,
410 			    "pf_anchor_copyout: '%s' '%s'", a,
411 			    r->anchor->path);
412 			rs_free(a, MAXPATHLEN);
413 			return (1);
414 		}
415 		if (strlen(r->anchor->path) > strlen(a))
416 			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
417 			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
418 		rs_free(a, MAXPATHLEN);
419 	}
420 	if (r->anchor_wildcard)
421 		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
422 		    sizeof(pr->anchor_call));
423 	return (0);
424 }
425 
426 void
pf_remove_anchor(struct pf_rule * r)427 pf_remove_anchor(struct pf_rule *r)
428 {
429 	if (r->anchor == NULL)
430 		return;
431 	if (r->anchor->refcnt <= 0)
432 		DPFPRINTF(LOG_NOTICE, "pf_remove_anchor: broken refcount");
433 	else if (!--r->anchor->refcnt)
434 		pf_remove_if_empty_ruleset(&r->anchor->ruleset);
435 	r->anchor = NULL;
436 }
437 
438 void
pf_anchor_rele(struct pf_anchor * anchor)439 pf_anchor_rele(struct pf_anchor *anchor)
440 {
441 	if ((anchor == NULL) || (anchor == &pf_main_anchor))
442 		return;
443 
444 #ifdef	_KERNEL
445 	if (refcnt_rele(&anchor->ref))
446 		rs_pool_put_anchor(anchor);
447 #else
448 	rs_pool_put_anchor(anchor);
449 #endif
450 }
451 
452 struct pf_anchor *
pf_anchor_take(struct pf_anchor * anchor)453 pf_anchor_take(struct pf_anchor *anchor)
454 {
455 #ifdef	_KERNEL
456 	if (anchor != NULL && anchor != &pf_main_anchor)
457 		refcnt_take(&anchor->ref);
458 #endif
459 	return (anchor);
460 }
461