xref: /freebsd/sys/net/pfil.c (revision a7dea1671b87c07d2d266f836bfa8b58efc7c134)
1 /*	$FreeBSD$ */
2 /*	$NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $	*/
3 
4 /*-
5  * SPDX-License-Identifier: BSD-3-Clause
6  *
7  * Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
8  * Copyright (c) 1996 Matthew R. Green
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. The name of the author may not be used to endorse or promote products
20  *    derived from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/conf.h>
37 #include <sys/kernel.h>
38 #include <sys/epoch.h>
39 #include <sys/errno.h>
40 #include <sys/lock.h>
41 #include <sys/malloc.h>
42 #include <sys/socket.h>
43 #include <sys/socketvar.h>
44 #include <sys/systm.h>
45 #include <sys/lock.h>
46 #include <sys/mutex.h>
47 #include <sys/proc.h>
48 #include <sys/queue.h>
49 #include <sys/ucred.h>
50 #include <sys/jail.h>
51 
52 #include <net/if.h>
53 #include <net/if_var.h>
54 #include <net/pfil.h>
55 
56 static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks");
57 
58 static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
59 static struct cdevsw pfil_cdevsw = {
60 	.d_ioctl =	pfil_ioctl,
61 	.d_name =	PFILDEV,
62 	.d_version =	D_VERSION,
63 };
64 static struct cdev *pfil_dev;
65 
66 static struct mtx pfil_lock;
67 MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF);
68 #define	PFIL_LOCK()	mtx_lock(&pfil_lock)
69 #define	PFIL_UNLOCK()	mtx_unlock(&pfil_lock)
70 #define	PFIL_LOCK_ASSERT()	mtx_assert(&pfil_lock, MA_OWNED)
71 
72 #define	PFIL_EPOCH		net_epoch_preempt
73 #define	PFIL_EPOCH_ENTER(et)	epoch_enter_preempt(net_epoch_preempt, &(et))
74 #define	PFIL_EPOCH_EXIT(et)	epoch_exit_preempt(net_epoch_preempt, &(et))
75 
76 struct pfil_hook {
77 	pfil_func_t	 hook_func;
78 	void		*hook_ruleset;
79 	int		 hook_flags;
80 	int		 hook_links;
81 	enum pfil_types	 hook_type;
82 	const char	*hook_modname;
83 	const char	*hook_rulname;
84 	LIST_ENTRY(pfil_hook) hook_list;
85 };
86 
87 struct pfil_link {
88 	CK_STAILQ_ENTRY(pfil_link) link_chain;
89 	pfil_func_t		 link_func;
90 	void			*link_ruleset;
91 	int			 link_flags;
92 	struct pfil_hook	*link_hook;
93 	struct epoch_context	 link_epoch_ctx;
94 };
95 
96 typedef CK_STAILQ_HEAD(pfil_chain, pfil_link)	pfil_chain_t;
97 struct pfil_head {
98 	int		 head_nhooksin;
99 	int		 head_nhooksout;
100 	pfil_chain_t	 head_in;
101 	pfil_chain_t	 head_out;
102 	int		 head_flags;
103 	enum pfil_types	 head_type;
104 	LIST_ENTRY(pfil_head) head_list;
105 	const char	*head_name;
106 };
107 
108 LIST_HEAD(pfilheadhead, pfil_head);
109 VNET_DEFINE_STATIC(struct pfilheadhead, pfil_head_list) =
110     LIST_HEAD_INITIALIZER(pfil_head_list);
111 #define	V_pfil_head_list	VNET(pfil_head_list)
112 
113 LIST_HEAD(pfilhookhead, pfil_hook);
114 VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) =
115     LIST_HEAD_INITIALIZER(pfil_hook_list);
116 #define	V_pfil_hook_list	VNET(pfil_hook_list)
117 
118 static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
119 static void pfil_link_free(epoch_context_t);
120 
121 int
122 pfil_realloc(pfil_packet_t *p, int flags, struct ifnet *ifp)
123 {
124 	struct mbuf *m;
125 
126 	MPASS(flags & PFIL_MEMPTR);
127 
128 	if ((m = m_devget(p->mem, PFIL_LENGTH(flags), 0, ifp, NULL)) == NULL)
129 		return (ENOMEM);
130 	*p = pfil_packet_align(*p);
131 	*p->m = m;
132 
133 	return (0);
134 }
135 
136 static __noinline int
137 pfil_fake_mbuf(pfil_func_t func, pfil_packet_t *p, struct ifnet *ifp, int flags,
138     void *ruleset, struct inpcb *inp)
139 {
140 	struct mbuf m, *mp;
141 	pfil_return_t rv;
142 
143 	(void)m_init(&m, M_NOWAIT, MT_DATA, M_NOFREE | M_PKTHDR);
144 	m_extadd(&m, p->mem, PFIL_LENGTH(flags), NULL, NULL, NULL, 0,
145 	    EXT_RXRING);
146 	m.m_len = m.m_pkthdr.len = PFIL_LENGTH(flags);
147 	mp = &m;
148 	flags &= ~(PFIL_MEMPTR | PFIL_LENMASK);
149 
150 	rv = func(&mp, ifp, flags, ruleset, inp);
151 	if (rv == PFIL_PASS && mp != &m) {
152 		/*
153 		 * Firewalls that need pfil_fake_mbuf() most likely don't
154 		 * know they need return PFIL_REALLOCED.
155 		 */
156 		rv = PFIL_REALLOCED;
157 		*p = pfil_packet_align(*p);
158 		*p->m = mp;
159 	}
160 
161 	return (rv);
162 }
163 
164 /*
165  * pfil_run_hooks() runs the specified packet filter hook chain.
166  */
167 int
168 pfil_run_hooks(struct pfil_head *head, pfil_packet_t p, struct ifnet *ifp,
169     int flags, struct inpcb *inp)
170 {
171 	struct epoch_tracker et;
172 	pfil_chain_t *pch;
173 	struct pfil_link *link;
174 	pfil_return_t rv;
175 	bool realloc = false;
176 
177 	if (PFIL_DIR(flags) == PFIL_IN)
178 		pch = &head->head_in;
179 	else if (__predict_true(PFIL_DIR(flags) == PFIL_OUT))
180 		pch = &head->head_out;
181 	else
182 		panic("%s: bogus flags %d", __func__, flags);
183 
184 	rv = PFIL_PASS;
185 	PFIL_EPOCH_ENTER(et);
186 	CK_STAILQ_FOREACH(link, pch, link_chain) {
187 		if ((flags & PFIL_MEMPTR) && !(link->link_flags & PFIL_MEMPTR))
188 			rv = pfil_fake_mbuf(link->link_func, &p, ifp, flags,
189 			    link->link_ruleset, inp);
190 		else
191 			rv = (*link->link_func)(p, ifp, flags,
192 			    link->link_ruleset, inp);
193 		if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED)
194 			break;
195 		else if (rv == PFIL_REALLOCED) {
196 			flags &= ~(PFIL_MEMPTR | PFIL_LENMASK);
197 			realloc = true;
198 		}
199 	}
200 	PFIL_EPOCH_EXIT(et);
201 	if (realloc && rv == PFIL_PASS)
202 		rv = PFIL_REALLOCED;
203 	return (rv);
204 }
205 
206 /*
207  * pfil_head_register() registers a pfil_head with the packet filter hook
208  * mechanism.
209  */
210 pfil_head_t
211 pfil_head_register(struct pfil_head_args *pa)
212 {
213 	struct pfil_head *head, *list;
214 
215 	MPASS(pa->pa_version == PFIL_VERSION);
216 
217 	head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
218 
219 	head->head_nhooksin = head->head_nhooksout = 0;
220 	head->head_flags = pa->pa_flags;
221 	head->head_type = pa->pa_type;
222 	head->head_name = pa->pa_headname;
223 	CK_STAILQ_INIT(&head->head_in);
224 	CK_STAILQ_INIT(&head->head_out);
225 
226 	PFIL_LOCK();
227 	LIST_FOREACH(list, &V_pfil_head_list, head_list)
228 		if (strcmp(pa->pa_headname, list->head_name) == 0) {
229 			printf("pfil: duplicate head \"%s\"\n",
230 			    pa->pa_headname);
231 		}
232 	LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
233 	PFIL_UNLOCK();
234 
235 	return (head);
236 }
237 
238 /*
239  * pfil_head_unregister() removes a pfil_head from the packet filter hook
240  * mechanism.  The producer of the hook promises that all outstanding
241  * invocations of the hook have completed before it unregisters the hook.
242  */
243 void
244 pfil_head_unregister(pfil_head_t ph)
245 {
246 	struct pfil_link *link, *next;
247 
248 	PFIL_LOCK();
249 	LIST_REMOVE(ph, head_list);
250 
251 	CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
252 		link->link_hook->hook_links--;
253 		free(link, M_PFIL);
254 	}
255 	CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
256 		link->link_hook->hook_links--;
257 		free(link, M_PFIL);
258 	}
259 	PFIL_UNLOCK();
260 }
261 
262 pfil_hook_t
263 pfil_add_hook(struct pfil_hook_args *pa)
264 {
265 	struct pfil_hook *hook, *list;
266 
267 	MPASS(pa->pa_version == PFIL_VERSION);
268 
269 	hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
270 	hook->hook_func = pa->pa_func;
271 	hook->hook_ruleset = pa->pa_ruleset;
272 	hook->hook_flags = pa->pa_flags;
273 	hook->hook_type = pa->pa_type;
274 	hook->hook_modname = pa->pa_modname;
275 	hook->hook_rulname = pa->pa_rulname;
276 
277 	PFIL_LOCK();
278 	LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
279 		if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
280 		    strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
281 			printf("pfil: duplicate hook \"%s:%s\"\n",
282 			    pa->pa_modname, pa->pa_rulname);
283 		}
284 	LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
285 	PFIL_UNLOCK();
286 
287 	return (hook);
288 }
289 
290 static int
291 pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
292 {
293 	struct pfil_link *in, *out;
294 
295 	PFIL_LOCK_ASSERT();
296 
297 	if (pa->pa_flags & PFIL_IN) {
298 		in = pfil_link_remove(&head->head_in, hook);
299 		if (in != NULL) {
300 			head->head_nhooksin--;
301 			hook->hook_links--;
302 		}
303 	} else
304 		in = NULL;
305 	if (pa->pa_flags & PFIL_OUT) {
306 		out = pfil_link_remove(&head->head_out, hook);
307 		if (out != NULL) {
308 			head->head_nhooksout--;
309 			hook->hook_links--;
310 		}
311 	} else
312 		out = NULL;
313 	PFIL_UNLOCK();
314 
315 	if (in != NULL)
316 		epoch_call(PFIL_EPOCH, pfil_link_free, &in->link_epoch_ctx);
317 	if (out != NULL)
318 		epoch_call(PFIL_EPOCH, pfil_link_free, &out->link_epoch_ctx);
319 
320 	if (in == NULL && out == NULL)
321 		return (ENOENT);
322 	else
323 		return (0);
324 }
325 
326 int
327 pfil_link(struct pfil_link_args *pa)
328 {
329 	struct pfil_link *in, *out, *link;
330 	struct pfil_head *head;
331 	struct pfil_hook *hook;
332 	int error;
333 
334 	MPASS(pa->pa_version == PFIL_VERSION);
335 
336 	if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN)
337 		in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO);
338 	else
339 		in = NULL;
340 	if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT)
341 		out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO);
342 	else
343 		out = NULL;
344 
345 	PFIL_LOCK();
346 	if (pa->pa_flags & PFIL_HEADPTR)
347 		head = pa->pa_head;
348 	else
349 		LIST_FOREACH(head, &V_pfil_head_list, head_list)
350 			if (strcmp(pa->pa_headname, head->head_name) == 0)
351 				break;
352 	if (pa->pa_flags & PFIL_HOOKPTR)
353 		hook = pa->pa_hook;
354 	else
355 		LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
356 			if (strcmp(pa->pa_modname, hook->hook_modname) == 0 &&
357 			    strcmp(pa->pa_rulname, hook->hook_rulname) == 0)
358 				break;
359 	if (head == NULL || hook == NULL) {
360 		error = ENOENT;
361 		goto fail;
362 	}
363 
364 	if (pa->pa_flags & PFIL_UNLINK)
365 		return (pfil_unlink(pa, head, hook));
366 
367 	if (head->head_type != hook->hook_type ||
368 	    ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) {
369 		error = EINVAL;
370 		goto fail;
371 	}
372 
373 	if (pa->pa_flags & PFIL_IN)
374 		CK_STAILQ_FOREACH(link, &head->head_in, link_chain)
375 			if (link->link_hook == hook) {
376 				error = EEXIST;
377 				goto fail;
378 			}
379 	if (pa->pa_flags & PFIL_OUT)
380 		CK_STAILQ_FOREACH(link, &head->head_out, link_chain)
381 			if (link->link_hook == hook) {
382 				error = EEXIST;
383 				goto fail;
384 			}
385 
386 	if (pa->pa_flags & PFIL_IN) {
387 		in->link_hook = hook;
388 		in->link_func = hook->hook_func;
389 		in->link_flags = hook->hook_flags;
390 		in->link_ruleset = hook->hook_ruleset;
391 		if (pa->pa_flags & PFIL_APPEND)
392 			CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain);
393 		else
394 			CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain);
395 		hook->hook_links++;
396 		head->head_nhooksin++;
397 	}
398 	if (pa->pa_flags & PFIL_OUT) {
399 		out->link_hook = hook;
400 		out->link_func = hook->hook_func;
401 		out->link_flags = hook->hook_flags;
402 		out->link_ruleset = hook->hook_ruleset;
403 		if (pa->pa_flags & PFIL_APPEND)
404 			CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain);
405 		else
406 			CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain);
407 		hook->hook_links++;
408 		head->head_nhooksout++;
409 	}
410 	PFIL_UNLOCK();
411 
412 	return (0);
413 
414 fail:
415 	PFIL_UNLOCK();
416 	free(in, M_PFIL);
417 	free(out, M_PFIL);
418 	return (error);
419 }
420 
421 static void
422 pfil_link_free(epoch_context_t ctx)
423 {
424 	struct pfil_link *link;
425 
426 	link = __containerof(ctx, struct pfil_link, link_epoch_ctx);
427 	free(link, M_PFIL);
428 }
429 
430 /*
431  * pfil_remove_hook removes a filter from all filtering points.
432  */
433 void
434 pfil_remove_hook(pfil_hook_t hook)
435 {
436 	struct pfil_head *head;
437 	struct pfil_link *in, *out;
438 
439 	PFIL_LOCK();
440 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
441 retry:
442 		in = pfil_link_remove(&head->head_in, hook);
443 		if (in != NULL) {
444 			head->head_nhooksin--;
445 			hook->hook_links--;
446 			epoch_call(PFIL_EPOCH, pfil_link_free,
447 			    &in->link_epoch_ctx);
448 		}
449 		out = pfil_link_remove(&head->head_out, hook);
450 		if (out != NULL) {
451 			head->head_nhooksout--;
452 			hook->hook_links--;
453 			epoch_call(PFIL_EPOCH, pfil_link_free,
454 			    &out->link_epoch_ctx);
455 		}
456 		if (in != NULL || out != NULL)
457 			/* What if some stupid admin put same filter twice? */
458 			goto retry;
459 	}
460 	LIST_REMOVE(hook, hook_list);
461 	PFIL_UNLOCK();
462 	MPASS(hook->hook_links == 0);
463 	free(hook, M_PFIL);
464 }
465 
466 /*
467  * Internal: Remove a pfil hook from a hook chain.
468  */
469 static struct pfil_link *
470 pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook)
471 {
472 	struct pfil_link *link;
473 
474 	PFIL_LOCK_ASSERT();
475 
476 	CK_STAILQ_FOREACH(link, chain, link_chain)
477 		if (link->link_hook == hook) {
478 			CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
479 			return (link);
480 		}
481 
482 	return (NULL);
483 }
484 
485 static void
486 pfil_init(const void *unused __unused)
487 {
488 	struct make_dev_args args;
489 	int error;
490 
491 	make_dev_args_init(&args);
492 	args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
493 	args.mda_devsw = &pfil_cdevsw;
494 	args.mda_uid = UID_ROOT;
495 	args.mda_gid = GID_WHEEL;
496 	args.mda_mode = 0600;
497 	error = make_dev_s(&args, &pfil_dev, PFILDEV);
498 	KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error));
499 }
500 /*
501  * Make sure the pfil bits are first before any possible subsystem which
502  * might piggyback on the SI_SUB_PROTO_PFIL.
503  */
504 SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
505 
506 /*
507  * User control interface.
508  */
509 static int pfilioc_listheads(struct pfilioc_list *);
510 static int pfilioc_listhooks(struct pfilioc_list *);
511 static int pfilioc_link(struct pfilioc_link *);
512 
513 static int
514 pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
515     struct thread *td)
516 {
517 	int error;
518 
519 	CURVNET_SET(TD_TO_VNET(td));
520 	error = 0;
521 	switch (cmd) {
522 	case PFILIOC_LISTHEADS:
523 		error = pfilioc_listheads((struct pfilioc_list *)addr);
524 		break;
525 	case PFILIOC_LISTHOOKS:
526 		error = pfilioc_listhooks((struct pfilioc_list *)addr);
527 		break;
528 	case PFILIOC_LINK:
529 		error = pfilioc_link((struct pfilioc_link *)addr);
530 		break;
531 	default:
532 		error = EINVAL;
533 		break;
534 	}
535 	CURVNET_RESTORE();
536 	return (error);
537 }
538 
539 static int
540 pfilioc_listheads(struct pfilioc_list *req)
541 {
542 	struct pfil_head *head;
543 	struct pfil_link *link;
544 	struct pfilioc_head *iohead;
545 	struct pfilioc_hook *iohook;
546 	u_int nheads, nhooks, hd, hk;
547 	int error;
548 
549 	PFIL_LOCK();
550 restart:
551 	nheads = nhooks = 0;
552 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
553 		nheads++;
554 		nhooks += head->head_nhooksin + head->head_nhooksout;
555 	}
556 	PFIL_UNLOCK();
557 
558 	if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) {
559 		req->pio_nheads = nheads;
560 		req->pio_nhooks = nhooks;
561 		return (0);
562 	}
563 
564 	iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK);
565 	iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
566 
567 	hd = hk = 0;
568 	PFIL_LOCK();
569 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
570 		if (hd + 1 > nheads ||
571 		    hk + head->head_nhooksin + head->head_nhooksout > nhooks) {
572 			/* Configuration changed during malloc(). */
573 			free(iohead, M_TEMP);
574 			free(iohook, M_TEMP);
575 			goto restart;
576 		}
577 		strlcpy(iohead[hd].pio_name, head->head_name,
578 			sizeof(iohead[0].pio_name));
579 		iohead[hd].pio_nhooksin = head->head_nhooksin;
580 		iohead[hd].pio_nhooksout = head->head_nhooksout;
581 		iohead[hd].pio_type = head->head_type;
582 		CK_STAILQ_FOREACH(link, &head->head_in, link_chain) {
583 			strlcpy(iohook[hk].pio_module,
584 			    link->link_hook->hook_modname,
585 			    sizeof(iohook[0].pio_module));
586 			strlcpy(iohook[hk].pio_ruleset,
587 			    link->link_hook->hook_rulname,
588 			    sizeof(iohook[0].pio_ruleset));
589 			hk++;
590 		}
591 		CK_STAILQ_FOREACH(link, &head->head_out, link_chain) {
592 			strlcpy(iohook[hk].pio_module,
593 			    link->link_hook->hook_modname,
594 			    sizeof(iohook[0].pio_module));
595 			strlcpy(iohook[hk].pio_ruleset,
596 			    link->link_hook->hook_rulname,
597 			    sizeof(iohook[0].pio_ruleset));
598 			hk++;
599 		}
600 		hd++;
601 	}
602 	PFIL_UNLOCK();
603 
604 	error = copyout(iohead, req->pio_heads,
605 	    sizeof(*iohead) * min(hd, req->pio_nheads));
606 	if (error == 0)
607 		error = copyout(iohook, req->pio_hooks,
608 		    sizeof(*iohook) * min(req->pio_nhooks, hk));
609 
610 	req->pio_nheads = hd;
611 	req->pio_nhooks = hk;
612 
613 	free(iohead, M_TEMP);
614 	free(iohook, M_TEMP);
615 
616 	return (error);
617 }
618 
619 static int
620 pfilioc_listhooks(struct pfilioc_list *req)
621 {
622 	struct pfil_hook *hook;
623 	struct pfilioc_hook *iohook;
624 	u_int nhooks, hk;
625 	int error;
626 
627 	PFIL_LOCK();
628 restart:
629 	nhooks = 0;
630 	LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
631 		nhooks++;
632 	PFIL_UNLOCK();
633 
634 	if (req->pio_nhooks < nhooks) {
635 		req->pio_nhooks = nhooks;
636 		return (0);
637 	}
638 
639 	iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
640 
641 	hk = 0;
642 	PFIL_LOCK();
643 	LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) {
644 		if (hk + 1 > nhooks) {
645 			/* Configuration changed during malloc(). */
646 			free(iohook, M_TEMP);
647 			goto restart;
648 		}
649 		strlcpy(iohook[hk].pio_module, hook->hook_modname,
650 		    sizeof(iohook[0].pio_module));
651 		strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname,
652 		    sizeof(iohook[0].pio_ruleset));
653 		iohook[hk].pio_type = hook->hook_type;
654 		iohook[hk].pio_flags = hook->hook_flags;
655 		hk++;
656 	}
657 	PFIL_UNLOCK();
658 
659 	error = copyout(iohook, req->pio_hooks,
660 	    sizeof(*iohook) * min(req->pio_nhooks, hk));
661 	req->pio_nhooks = hk;
662 	free(iohook, M_TEMP);
663 
664 	return (error);
665 }
666 
667 static int
668 pfilioc_link(struct pfilioc_link *req)
669 {
670 	struct pfil_link_args args;
671 
672 	if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND))
673 		return (EINVAL);
674 
675 	args.pa_version = PFIL_VERSION;
676 	args.pa_flags = req->pio_flags;
677 	args.pa_headname = req->pio_name;
678 	args.pa_modname = req->pio_module;
679 	args.pa_rulname = req->pio_ruleset;
680 
681 	return (pfil_link(&args));
682 }
683