1 /* $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $ */
4 * SPDX-License-Identifier: BSD-3-Clause
6 * Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
7 * Copyright (c) 1996 Matthew R. Green
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. The name of the author may not be used to endorse or promote products
19 * derived from this software without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 #include <sys/param.h>
36 #include <sys/kernel.h>
37 #include <sys/epoch.h>
38 #include <sys/errno.h>
40 #include <sys/malloc.h>
41 #include <sys/socket.h>
42 #include <sys/socketvar.h>
43 #include <sys/systm.h>
45 #include <sys/mutex.h>
47 #include <sys/queue.h>
48 #include <sys/ucred.h>
52 #include <net/if_var.h>
55 static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks");
57 static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
58 static struct cdevsw pfil_cdevsw = {
59 .d_ioctl = pfil_ioctl,
61 .d_version = D_VERSION,
63 static struct cdev *pfil_dev;
65 static struct mtx pfil_lock;
66 MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF);
67 #define PFIL_LOCK() mtx_lock(&pfil_lock)
68 #define PFIL_UNLOCK() mtx_unlock(&pfil_lock)
69 #define PFIL_LOCK_ASSERT() mtx_assert(&pfil_lock, MA_OWNED)
72 pfil_mbuf_chk_t hook_mbuf_chk;
73 pfil_mem_chk_t hook_mem_chk;
77 enum pfil_types hook_type;
78 const char *hook_modname;
79 const char *hook_rulname;
80 LIST_ENTRY(pfil_hook) hook_list;
84 CK_STAILQ_ENTRY(pfil_link) link_chain;
85 pfil_mbuf_chk_t link_mbuf_chk;
86 pfil_mem_chk_t link_mem_chk;
89 struct pfil_hook *link_hook;
90 struct epoch_context link_epoch_ctx;
93 typedef CK_STAILQ_HEAD(pfil_chain, pfil_link) pfil_chain_t;
98 pfil_chain_t head_out;
100 enum pfil_types head_type;
101 LIST_ENTRY(pfil_head) head_list;
102 const char *head_name;
105 LIST_HEAD(pfilheadhead, pfil_head);
106 VNET_DEFINE_STATIC(struct pfilheadhead, pfil_head_list) =
107 LIST_HEAD_INITIALIZER(pfil_head_list);
108 #define V_pfil_head_list VNET(pfil_head_list)
110 LIST_HEAD(pfilhookhead, pfil_hook);
111 VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) =
112 LIST_HEAD_INITIALIZER(pfil_hook_list);
113 #define V_pfil_hook_list VNET(pfil_hook_list)
115 static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
116 static void pfil_link_free(epoch_context_t);
119 * To couple a filtering point that provides memory pointer with a filter that
120 * works on mbufs only.
122 static __noinline int
123 pfil_fake_mbuf(pfil_mbuf_chk_t func, void *mem, u_int len, struct ifnet *ifp,
124 int flags, void *ruleset, struct mbuf **mp)
129 (void)m_init(&m, M_NOWAIT, MT_DATA, M_NOFREE | M_PKTHDR);
130 m_extadd(&m, mem, len, NULL, NULL, NULL, 0, EXT_RXRING);
131 m.m_len = m.m_pkthdr.len = len;
134 rv = func(mp, ifp, flags, ruleset, NULL);
135 if (rv == PFIL_PASS && *mp != &m) {
137 * Firewalls that need pfil_fake_mbuf() most likely don't
138 * know they need return PFIL_REALLOCED.
146 static __always_inline int
147 pfil_mem_common(pfil_chain_t *pch, void *mem, u_int len, int flags,
148 struct ifnet *ifp, struct mbuf **m)
150 struct pfil_link *link;
152 bool realloc = false;
155 KASSERT(flags == PFIL_IN || flags == PFIL_OUT,
156 ("%s: unsupported flags %d", __func__, flags));
159 CK_STAILQ_FOREACH(link, pch, link_chain) {
160 if (__predict_true(link->link_mem_chk != NULL && !realloc))
161 rv = link->link_mem_chk(mem, len, flags, ifp,
162 link->link_ruleset, m);
164 rv = pfil_fake_mbuf(link->link_mbuf_chk, mem, len, ifp,
165 flags, link->link_ruleset, m);
167 rv = link->link_mbuf_chk(m, ifp, flags,
168 link->link_ruleset, NULL);
170 if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED)
172 else if (rv == PFIL_REALLOCED)
175 if (realloc && rv == PFIL_PASS)
181 pfil_mem_in(struct pfil_head *head, void *mem, u_int len, struct ifnet *ifp,
185 return (pfil_mem_common(&head->head_in, mem, len, PFIL_IN, ifp, m));
189 pfil_mem_out(struct pfil_head *head, void *mem, u_int len, struct ifnet *ifp,
193 return (pfil_mem_common(&head->head_out, mem, len, PFIL_OUT, ifp, m));
196 static __always_inline int
197 pfil_mbuf_common(pfil_chain_t *pch, struct mbuf **m, struct ifnet *ifp,
198 int flags, struct inpcb *inp)
200 struct pfil_link *link;
204 KASSERT((flags & ~(PFIL_IN|PFIL_OUT|PFIL_FWD)) == 0,
205 ("%s: unsupported flags %#x", __func__, flags));
206 KASSERT((flags & ~PFIL_FWD) == PFIL_IN ||
207 (flags & ~PFIL_FWD) == PFIL_OUT,
208 ("%s: conflicting directions %#x", __func__, flags));
211 CK_STAILQ_FOREACH(link, pch, link_chain) {
212 rv = link->link_mbuf_chk(m, ifp, flags, link->link_ruleset,
214 if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED) {
226 pfil_mbuf_in(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
230 return (pfil_mbuf_common(&head->head_in, m, ifp, PFIL_IN, inp));
234 pfil_mbuf_out(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
238 return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT, inp));
242 pfil_mbuf_fwd(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
246 return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT | PFIL_FWD, inp));
250 * pfil_head_register() registers a pfil_head with the packet filter hook
254 pfil_head_register(struct pfil_head_args *pa)
256 struct pfil_head *head, *list;
258 MPASS(pa->pa_version == PFIL_VERSION);
260 head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
262 head->head_nhooksin = head->head_nhooksout = 0;
263 head->head_flags = pa->pa_flags;
264 head->head_type = pa->pa_type;
265 head->head_name = pa->pa_headname;
266 CK_STAILQ_INIT(&head->head_in);
267 CK_STAILQ_INIT(&head->head_out);
270 LIST_FOREACH(list, &V_pfil_head_list, head_list)
271 if (strcmp(pa->pa_headname, list->head_name) == 0) {
272 printf("pfil: duplicate head \"%s\"\n",
275 LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
282 * pfil_head_unregister() removes a pfil_head from the packet filter hook
283 * mechanism. The producer of the hook promises that all outstanding
284 * invocations of the hook have completed before it unregisters the hook.
287 pfil_head_unregister(pfil_head_t ph)
289 struct pfil_link *link, *next;
292 LIST_REMOVE(ph, head_list);
294 CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
295 link->link_hook->hook_links--;
298 CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
299 link->link_hook->hook_links--;
307 pfil_add_hook(struct pfil_hook_args *pa)
309 struct pfil_hook *hook, *list;
311 MPASS(pa->pa_version == PFIL_VERSION);
313 hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
314 hook->hook_mbuf_chk = pa->pa_mbuf_chk;
315 hook->hook_mem_chk = pa->pa_mem_chk;
316 hook->hook_ruleset = pa->pa_ruleset;
317 hook->hook_flags = pa->pa_flags;
318 hook->hook_type = pa->pa_type;
319 hook->hook_modname = pa->pa_modname;
320 hook->hook_rulname = pa->pa_rulname;
323 LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
324 if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
325 strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
326 printf("pfil: duplicate hook \"%s:%s\"\n",
327 pa->pa_modname, pa->pa_rulname);
329 LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
336 pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
338 struct pfil_link *in, *out;
342 if (pa->pa_flags & PFIL_IN) {
343 in = pfil_link_remove(&head->head_in, hook);
345 head->head_nhooksin--;
350 if (pa->pa_flags & PFIL_OUT) {
351 out = pfil_link_remove(&head->head_out, hook);
353 head->head_nhooksout--;
361 NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
363 NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
365 if (in == NULL && out == NULL)
372 pfil_link(struct pfil_link_args *pa)
374 struct pfil_link *in, *out, *link;
375 struct pfil_head *head;
376 struct pfil_hook *hook;
379 MPASS(pa->pa_version == PFIL_VERSION);
381 if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN)
382 in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO);
385 if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT)
386 out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO);
391 if (pa->pa_flags & PFIL_HEADPTR)
394 LIST_FOREACH(head, &V_pfil_head_list, head_list)
395 if (strcmp(pa->pa_headname, head->head_name) == 0)
397 if (pa->pa_flags & PFIL_HOOKPTR)
400 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
401 if (strcmp(pa->pa_modname, hook->hook_modname) == 0 &&
402 strcmp(pa->pa_rulname, hook->hook_rulname) == 0)
404 if (head == NULL || hook == NULL) {
409 if (pa->pa_flags & PFIL_UNLINK)
410 return (pfil_unlink(pa, head, hook));
412 if (head->head_type != hook->hook_type ||
413 ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) {
418 if (pa->pa_flags & PFIL_IN)
419 CK_STAILQ_FOREACH(link, &head->head_in, link_chain)
420 if (link->link_hook == hook) {
424 if (pa->pa_flags & PFIL_OUT)
425 CK_STAILQ_FOREACH(link, &head->head_out, link_chain)
426 if (link->link_hook == hook) {
431 if (pa->pa_flags & PFIL_IN) {
432 in->link_hook = hook;
433 in->link_mbuf_chk = hook->hook_mbuf_chk;
434 in->link_mem_chk = hook->hook_mem_chk;
435 in->link_flags = hook->hook_flags;
436 in->link_ruleset = hook->hook_ruleset;
437 if (pa->pa_flags & PFIL_APPEND)
438 CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain);
440 CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain);
442 head->head_nhooksin++;
444 if (pa->pa_flags & PFIL_OUT) {
445 out->link_hook = hook;
446 out->link_mbuf_chk = hook->hook_mbuf_chk;
447 out->link_mem_chk = hook->hook_mem_chk;
448 out->link_flags = hook->hook_flags;
449 out->link_ruleset = hook->hook_ruleset;
450 if (pa->pa_flags & PFIL_APPEND)
451 CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain);
453 CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain);
455 head->head_nhooksout++;
469 pfil_link_free(epoch_context_t ctx)
471 struct pfil_link *link;
473 link = __containerof(ctx, struct pfil_link, link_epoch_ctx);
478 * pfil_remove_hook removes a filter from all filtering points.
481 pfil_remove_hook(pfil_hook_t hook)
483 struct pfil_head *head;
484 struct pfil_link *in, *out;
487 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
489 in = pfil_link_remove(&head->head_in, hook);
491 head->head_nhooksin--;
493 NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
495 out = pfil_link_remove(&head->head_out, hook);
497 head->head_nhooksout--;
499 NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
501 if (in != NULL || out != NULL)
502 /* What if some stupid admin put same filter twice? */
505 LIST_REMOVE(hook, hook_list);
507 MPASS(hook->hook_links == 0);
512 * Internal: Remove a pfil hook from a hook chain.
514 static struct pfil_link *
515 pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook)
517 struct pfil_link *link;
521 CK_STAILQ_FOREACH(link, chain, link_chain)
522 if (link->link_hook == hook) {
523 CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
531 pfil_init(const void *unused __unused)
533 struct make_dev_args args;
534 int error __diagused;
536 make_dev_args_init(&args);
537 args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
538 args.mda_devsw = &pfil_cdevsw;
539 args.mda_uid = UID_ROOT;
540 args.mda_gid = GID_WHEEL;
541 args.mda_mode = 0600;
542 error = make_dev_s(&args, &pfil_dev, PFILDEV);
543 KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error));
546 * Make sure the pfil bits are first before any possible subsystem which
547 * might piggyback on the SI_SUB_PROTO_PFIL.
549 SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
552 * User control interface.
554 static int pfilioc_listheads(struct pfilioc_list *);
555 static int pfilioc_listhooks(struct pfilioc_list *);
556 static int pfilioc_link(struct pfilioc_link *);
559 pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
564 CURVNET_SET(TD_TO_VNET(td));
567 case PFILIOC_LISTHEADS:
568 error = pfilioc_listheads((struct pfilioc_list *)addr);
570 case PFILIOC_LISTHOOKS:
571 error = pfilioc_listhooks((struct pfilioc_list *)addr);
574 error = pfilioc_link((struct pfilioc_link *)addr);
585 pfilioc_listheads(struct pfilioc_list *req)
587 struct pfil_head *head;
588 struct pfil_link *link;
589 struct pfilioc_head *iohead;
590 struct pfilioc_hook *iohook;
591 u_int nheads, nhooks, hd, hk;
597 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
599 nhooks += head->head_nhooksin + head->head_nhooksout;
603 if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) {
604 req->pio_nheads = nheads;
605 req->pio_nhooks = nhooks;
609 iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK);
610 iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
614 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
615 if (hd + 1 > nheads ||
616 hk + head->head_nhooksin + head->head_nhooksout > nhooks) {
617 /* Configuration changed during malloc(). */
618 free(iohead, M_TEMP);
619 free(iohook, M_TEMP);
622 strlcpy(iohead[hd].pio_name, head->head_name,
623 sizeof(iohead[0].pio_name));
624 iohead[hd].pio_nhooksin = head->head_nhooksin;
625 iohead[hd].pio_nhooksout = head->head_nhooksout;
626 iohead[hd].pio_type = head->head_type;
627 CK_STAILQ_FOREACH(link, &head->head_in, link_chain) {
628 strlcpy(iohook[hk].pio_module,
629 link->link_hook->hook_modname,
630 sizeof(iohook[0].pio_module));
631 strlcpy(iohook[hk].pio_ruleset,
632 link->link_hook->hook_rulname,
633 sizeof(iohook[0].pio_ruleset));
636 CK_STAILQ_FOREACH(link, &head->head_out, link_chain) {
637 strlcpy(iohook[hk].pio_module,
638 link->link_hook->hook_modname,
639 sizeof(iohook[0].pio_module));
640 strlcpy(iohook[hk].pio_ruleset,
641 link->link_hook->hook_rulname,
642 sizeof(iohook[0].pio_ruleset));
649 error = copyout(iohead, req->pio_heads,
650 sizeof(*iohead) * min(hd, req->pio_nheads));
652 error = copyout(iohook, req->pio_hooks,
653 sizeof(*iohook) * min(req->pio_nhooks, hk));
655 req->pio_nheads = hd;
656 req->pio_nhooks = hk;
658 free(iohead, M_TEMP);
659 free(iohook, M_TEMP);
665 pfilioc_listhooks(struct pfilioc_list *req)
667 struct pfil_hook *hook;
668 struct pfilioc_hook *iohook;
675 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
679 if (req->pio_nhooks < nhooks) {
680 req->pio_nhooks = nhooks;
684 iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
688 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) {
689 if (hk + 1 > nhooks) {
690 /* Configuration changed during malloc(). */
691 free(iohook, M_TEMP);
694 strlcpy(iohook[hk].pio_module, hook->hook_modname,
695 sizeof(iohook[0].pio_module));
696 strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname,
697 sizeof(iohook[0].pio_ruleset));
698 iohook[hk].pio_type = hook->hook_type;
699 iohook[hk].pio_flags = hook->hook_flags;
704 error = copyout(iohook, req->pio_hooks,
705 sizeof(*iohook) * min(req->pio_nhooks, hk));
706 req->pio_nhooks = hk;
707 free(iohook, M_TEMP);
713 pfilioc_link(struct pfilioc_link *req)
715 struct pfil_link_args args;
717 if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND))
720 args.pa_version = PFIL_VERSION;
721 args.pa_flags = req->pio_flags;
722 args.pa_headname = req->pio_name;
723 args.pa_modname = req->pio_module;
724 args.pa_rulname = req->pio_ruleset;
726 return (pfil_link(&args));