xref: /openbsd-src/sys/net/pf_ruleset.c (revision 25c4e8bd056e974b28f4a0ffd39d76c190a56013)
1 /*	$OpenBSD: pf_ruleset.c,v 1.19 2022/07/20 09:33:11 mbuhl 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_TEMP, M_WAITOK|M_CANFAIL|M_ZERO)
61 #define rs_free(x, siz)		free(x, M_TEMP, 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
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
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 *
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 *
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 *
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 *
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 
237 	return (anchor);
238 }
239 
240 struct pf_ruleset *
241 pf_find_or_create_ruleset(const char *path)
242 {
243 	char			*p, *aname, *r;
244 	struct pf_ruleset	*ruleset;
245 	struct pf_anchor	*anchor;
246 
247 	if (path[0] == 0)
248 		return (&pf_main_ruleset);
249 
250 	while (*path == '/')
251 		path++;
252 
253 	ruleset = pf_find_ruleset(path);
254 	if (ruleset != NULL)
255 		return (ruleset);
256 
257 	p = rs_malloc(MAXPATHLEN);
258 	if (p == NULL)
259 		return (NULL);
260 	strlcpy(p, path, MAXPATHLEN);
261 
262 	ruleset = pf_get_leaf_ruleset(p, &aname);
263 	anchor = ruleset->anchor;
264 
265 	while (*aname == '/')
266 		aname++;
267 	/*
268 	 * aname is a path remainder, which contains nodes we must create.  We
269 	 * process the aname path from left to right, effectively descending
270 	 * from parents to children.
271 	 */
272 	while ((r = strchr(aname, '/')) != NULL || *aname) {
273 		if (r != NULL)
274 			*r = 0;
275 
276 		anchor = pf_create_anchor(anchor, aname);
277 		if (anchor == NULL) {
278 			rs_free(p, MAXPATHLEN);
279 			return (NULL);
280 		}
281 
282 		if (r == NULL)
283 			break;
284 		else
285 			aname = r + 1;
286 	}
287 
288 	rs_free(p, MAXPATHLEN);
289 	return (&anchor->ruleset);
290 }
291 
292 void
293 pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
294 {
295 	struct pf_anchor	*parent;
296 
297 	while (ruleset != NULL) {
298 		if (ruleset == &pf_main_ruleset ||
299 		    !RB_EMPTY(&ruleset->anchor->children) ||
300 		    ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
301 		    ruleset->topen)
302 			return;
303 		if (!TAILQ_EMPTY(ruleset->rules.active.ptr) ||
304 		    !TAILQ_EMPTY(ruleset->rules.inactive.ptr) ||
305 		    ruleset->rules.inactive.open)
306 			return;
307 		RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor);
308 		if ((parent = ruleset->anchor->parent) != NULL)
309 			RB_REMOVE(pf_anchor_node, &parent->children,
310 			    ruleset->anchor);
311 		rs_pool_put_anchor(ruleset->anchor);
312 		if (parent == NULL)
313 			return;
314 		ruleset = &parent->ruleset;
315 	}
316 }
317 
318 int
319 pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s,
320     const char *name)
321 {
322 	char			*p, *path;
323 	struct pf_ruleset	*ruleset;
324 
325 	r->anchor = NULL;
326 	r->anchor_relative = 0;
327 	r->anchor_wildcard = 0;
328 	if (!name[0])
329 		return (0);
330 	path = rs_malloc(MAXPATHLEN);
331 	if (path == NULL)
332 		return (1);
333 	if (name[0] == '/')
334 		strlcpy(path, name + 1, MAXPATHLEN);
335 	else {
336 		/* relative path */
337 		r->anchor_relative = 1;
338 		if (s->anchor == NULL || !s->anchor->path[0])
339 			path[0] = 0;
340 		else
341 			strlcpy(path, s->anchor->path, MAXPATHLEN);
342 		while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
343 			if (!path[0]) {
344 				DPFPRINTF(LOG_NOTICE,
345 				    "pf_anchor_setup: .. beyond root");
346 				rs_free(path, MAXPATHLEN);
347 				return (1);
348 			}
349 			if ((p = strrchr(path, '/')) != NULL)
350 				*p = 0;
351 			else
352 				path[0] = 0;
353 			r->anchor_relative++;
354 			name += 3;
355 		}
356 		if (path[0])
357 			strlcat(path, "/", MAXPATHLEN);
358 		strlcat(path, name, MAXPATHLEN);
359 	}
360 	if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
361 		r->anchor_wildcard = 1;
362 		*p = 0;
363 	}
364 	ruleset = pf_find_or_create_ruleset(path);
365 	rs_free(path, MAXPATHLEN);
366 	if (ruleset == NULL || ruleset == &pf_main_ruleset) {
367 		DPFPRINTF(LOG_NOTICE,
368 		    "pf_anchor_setup: ruleset");
369 		return (1);
370 	}
371 	r->anchor = ruleset->anchor;
372 	r->anchor->refcnt++;
373 	return (0);
374 }
375 
376 int
377 pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
378     struct pfioc_rule *pr)
379 {
380 	pr->anchor_call[0] = 0;
381 	if (r->anchor == NULL)
382 		return (0);
383 	if (!r->anchor_relative) {
384 		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
385 		strlcat(pr->anchor_call, r->anchor->path,
386 		    sizeof(pr->anchor_call));
387 	} else {
388 		char	*a, *p;
389 		int	 i;
390 
391 		a = rs_malloc(MAXPATHLEN);
392 		if (a == NULL)
393 			return (1);
394 		if (rs == &pf_main_ruleset)
395 			a[0] = 0;
396 		else
397 			strlcpy(a, rs->anchor->path, MAXPATHLEN);
398 		for (i = 1; i < r->anchor_relative; ++i) {
399 			if ((p = strrchr(a, '/')) == NULL)
400 				p = a;
401 			*p = 0;
402 			strlcat(pr->anchor_call, "../",
403 			    sizeof(pr->anchor_call));
404 		}
405 		if (strncmp(a, r->anchor->path, strlen(a))) {
406 			DPFPRINTF(LOG_NOTICE,
407 			    "pf_anchor_copyout: '%s' '%s'", a,
408 			    r->anchor->path);
409 			rs_free(a, MAXPATHLEN);
410 			return (1);
411 		}
412 		if (strlen(r->anchor->path) > strlen(a))
413 			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
414 			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
415 		rs_free(a, MAXPATHLEN);
416 	}
417 	if (r->anchor_wildcard)
418 		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
419 		    sizeof(pr->anchor_call));
420 	return (0);
421 }
422 
423 void
424 pf_remove_anchor(struct pf_rule *r)
425 {
426 	if (r->anchor == NULL)
427 		return;
428 	if (r->anchor->refcnt <= 0)
429 		DPFPRINTF(LOG_NOTICE, "pf_remove_anchor: broken refcount");
430 	else if (!--r->anchor->refcnt)
431 		pf_remove_if_empty_ruleset(&r->anchor->ruleset);
432 	r->anchor = NULL;
433 }
434