xref: /netbsd-src/sys/kern/kern_hook.c (revision 29964953ba4f69021f808034dc736f4b0b060a71)
1 /*	$NetBSD: kern_hook.c,v 1.15 2024/01/17 10:18:41 hannken Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997, 1998, 1999, 2002, 2007, 2008 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9  * NASA Ames Research Center, and by Luke Mewburn.
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  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: kern_hook.c,v 1.15 2024/01/17 10:18:41 hannken Exp $");
35 
36 #include <sys/param.h>
37 
38 #include <sys/condvar.h>
39 #include <sys/cpu.h>
40 #include <sys/device.h>
41 #include <sys/exec.h>
42 #include <sys/hook.h>
43 #include <sys/kmem.h>
44 #include <sys/malloc.h>
45 #include <sys/once.h>
46 #include <sys/rwlock.h>
47 #include <sys/systm.h>
48 
49 /*
50  * A generic linear hook.
51  */
52 struct hook_desc {
53 	LIST_ENTRY(hook_desc) hk_list;
54 	void	(*hk_fn)(void *);
55 	void	*hk_arg;
56 };
57 typedef LIST_HEAD(, hook_desc) hook_list_t;
58 
59 enum hook_list_st {
60 	HKLIST_IDLE,
61 	HKLIST_INUSE,
62 };
63 
64 struct khook_list {
65 	hook_list_t	 hl_list;
66 	kmutex_t	 hl_lock;
67 	kmutex_t	*hl_cvlock;
68 	struct lwp	*hl_lwp;
69 	kcondvar_t	 hl_cv;
70 	enum hook_list_st
71 			 hl_state;
72 	khook_t		*hl_active_hk;
73 	char		 hl_namebuf[HOOKNAMSIZ];
74 };
75 
76 int	powerhook_debug = 0;
77 
78 static ONCE_DECL(hook_control);
79 static krwlock_t exithook_lock;
80 static krwlock_t forkhook_lock;
81 
82 static int
hook_init(void)83 hook_init(void)
84 {
85 
86 	rw_init(&exithook_lock);
87 	rw_init(&forkhook_lock);
88 
89 	return 0;
90 }
91 
92 static void *
hook_establish(hook_list_t * list,krwlock_t * lock,void (* fn)(void *),void * arg)93 hook_establish(hook_list_t *list, krwlock_t *lock,
94     void (*fn)(void *), void *arg)
95 {
96 	struct hook_desc *hd;
97 
98 	RUN_ONCE(&hook_control, hook_init);
99 
100 	hd = malloc(sizeof(*hd), M_DEVBUF, M_NOWAIT);
101 	if (hd != NULL) {
102 		if (lock)
103 			rw_enter(lock, RW_WRITER);
104 		hd->hk_fn = fn;
105 		hd->hk_arg = arg;
106 		LIST_INSERT_HEAD(list, hd, hk_list);
107 		if (lock)
108 			rw_exit(lock);
109 	}
110 
111 	return (hd);
112 }
113 
114 static void
hook_disestablish(hook_list_t * list,krwlock_t * lock,void * vhook)115 hook_disestablish(hook_list_t *list, krwlock_t *lock, void *vhook)
116 {
117 
118 	if (lock)
119 		rw_enter(lock, RW_WRITER);
120 #ifdef DIAGNOSTIC
121 	struct hook_desc *hd;
122 
123 	LIST_FOREACH(hd, list, hk_list) {
124                 if (hd == vhook)
125 			break;
126 	}
127 
128 	if (hd == NULL)
129 		panic("hook_disestablish: hook %p not established", vhook);
130 #endif
131 	LIST_REMOVE((struct hook_desc *)vhook, hk_list);
132 	free(vhook, M_DEVBUF);
133 	if (lock)
134 		rw_exit(lock);
135 }
136 
137 static void
hook_destroy(hook_list_t * list)138 hook_destroy(hook_list_t *list)
139 {
140 	struct hook_desc *hd;
141 
142 	while ((hd = LIST_FIRST(list)) != NULL) {
143 		LIST_REMOVE(hd, hk_list);
144 		free(hd, M_DEVBUF);
145 	}
146 }
147 
148 static void
hook_proc_run(hook_list_t * list,krwlock_t * lock,struct proc * p)149 hook_proc_run(hook_list_t *list, krwlock_t *lock, struct proc *p)
150 {
151 	struct hook_desc *hd;
152 
153 	RUN_ONCE(&hook_control, hook_init);
154 
155 	if (lock)
156 		rw_enter(lock, RW_READER);
157 	LIST_FOREACH(hd, list, hk_list) {
158 		__FPTRCAST(void (*)(struct proc *, void *), *hd->hk_fn)(p,
159 		    hd->hk_arg);
160 	}
161 	if (lock)
162 		rw_exit(lock);
163 }
164 
165 /*
166  * "Shutdown hook" types, functions, and variables.
167  *
168  * Should be invoked immediately before the
169  * system is halted or rebooted, i.e. after file systems unmounted,
170  * after crash dump done, etc.
171  *
172  * Each shutdown hook is removed from the list before it's run, so that
173  * it won't be run again.
174  */
175 
176 static hook_list_t shutdownhook_list = LIST_HEAD_INITIALIZER(shutdownhook_list);
177 
178 void *
shutdownhook_establish(void (* fn)(void *),void * arg)179 shutdownhook_establish(void (*fn)(void *), void *arg)
180 {
181 	return hook_establish(&shutdownhook_list, NULL, fn, arg);
182 }
183 
184 void
shutdownhook_disestablish(void * vhook)185 shutdownhook_disestablish(void *vhook)
186 {
187 	hook_disestablish(&shutdownhook_list, NULL, vhook);
188 }
189 
190 /*
191  * Run shutdown hooks.  Should be invoked immediately before the
192  * system is halted or rebooted, i.e. after file systems unmounted,
193  * after crash dump done, etc.
194  *
195  * Each shutdown hook is removed from the list before it's run, so that
196  * it won't be run again.
197  */
198 void
doshutdownhooks(void)199 doshutdownhooks(void)
200 {
201 	struct hook_desc *dp;
202 
203 	while ((dp = LIST_FIRST(&shutdownhook_list)) != NULL) {
204 		LIST_REMOVE(dp, hk_list);
205 		(*dp->hk_fn)(dp->hk_arg);
206 #if 0
207 		/*
208 		 * Don't bother freeing the hook structure,, since we may
209 		 * be rebooting because of a memory corruption problem,
210 		 * and this might only make things worse.  It doesn't
211 		 * matter, anyway, since the system is just about to
212 		 * reboot.
213 		 */
214 		free(dp, M_DEVBUF);
215 #endif
216 	}
217 }
218 
219 /*
220  * "Mountroot hook" types, functions, and variables.
221  */
222 
223 static hook_list_t mountroothook_list=LIST_HEAD_INITIALIZER(mountroothook_list);
224 
225 void *
mountroothook_establish(void (* fn)(device_t),device_t dev)226 mountroothook_establish(void (*fn)(device_t), device_t dev)
227 {
228 	return hook_establish(&mountroothook_list, NULL,
229 	    __FPTRCAST(void (*), fn), dev);
230 }
231 
232 void
mountroothook_disestablish(void * vhook)233 mountroothook_disestablish(void *vhook)
234 {
235 	hook_disestablish(&mountroothook_list, NULL, vhook);
236 }
237 
238 void
mountroothook_destroy(void)239 mountroothook_destroy(void)
240 {
241 	hook_destroy(&mountroothook_list);
242 }
243 
244 void
domountroothook(device_t therootdev)245 domountroothook(device_t therootdev)
246 {
247 	struct hook_desc *hd;
248 
249 	LIST_FOREACH(hd, &mountroothook_list, hk_list) {
250 		if (hd->hk_arg == therootdev) {
251 			(*hd->hk_fn)(hd->hk_arg);
252 			return;
253 		}
254 	}
255 }
256 
257 static hook_list_t exechook_list = LIST_HEAD_INITIALIZER(exechook_list);
258 
259 void *
exechook_establish(void (* fn)(struct proc *,void *),void * arg)260 exechook_establish(void (*fn)(struct proc *, void *), void *arg)
261 {
262 	return hook_establish(&exechook_list, &exec_lock,
263 		__FPTRCAST(void (*)(void *), fn), arg);
264 }
265 
266 void
exechook_disestablish(void * vhook)267 exechook_disestablish(void *vhook)
268 {
269 	hook_disestablish(&exechook_list, &exec_lock, vhook);
270 }
271 
272 /*
273  * Run exec hooks.
274  */
275 void
doexechooks(struct proc * p)276 doexechooks(struct proc *p)
277 {
278 	KASSERT(rw_lock_held(&exec_lock));
279 
280 	hook_proc_run(&exechook_list, NULL, p);
281 }
282 
283 static hook_list_t exithook_list = LIST_HEAD_INITIALIZER(exithook_list);
284 
285 void *
exithook_establish(void (* fn)(struct proc *,void *),void * arg)286 exithook_establish(void (*fn)(struct proc *, void *), void *arg)
287 {
288 
289 	return hook_establish(&exithook_list, &exithook_lock,
290 	    __FPTRCAST(void (*)(void *), fn), arg);
291 }
292 
293 void
exithook_disestablish(void * vhook)294 exithook_disestablish(void *vhook)
295 {
296 
297 	hook_disestablish(&exithook_list, &exithook_lock, vhook);
298 }
299 
300 /*
301  * Run exit hooks.
302  */
303 void
doexithooks(struct proc * p)304 doexithooks(struct proc *p)
305 {
306 	hook_proc_run(&exithook_list, &exithook_lock, p);
307 }
308 
309 static hook_list_t forkhook_list = LIST_HEAD_INITIALIZER(forkhook_list);
310 
311 void *
forkhook_establish(void (* fn)(struct proc *,struct proc *))312 forkhook_establish(void (*fn)(struct proc *, struct proc *))
313 {
314 	return hook_establish(&forkhook_list, &forkhook_lock,
315 	    __FPTRCAST(void (*)(void *), fn), NULL);
316 }
317 
318 void
forkhook_disestablish(void * vhook)319 forkhook_disestablish(void *vhook)
320 {
321 	hook_disestablish(&forkhook_list, &forkhook_lock, vhook);
322 }
323 
324 /*
325  * Run fork hooks.
326  */
327 void
doforkhooks(struct proc * p2,struct proc * p1)328 doforkhooks(struct proc *p2, struct proc *p1)
329 {
330 	struct hook_desc *hd;
331 
332 	RUN_ONCE(&hook_control, hook_init);
333 
334 	rw_enter(&forkhook_lock, RW_READER);
335 	LIST_FOREACH(hd, &forkhook_list, hk_list) {
336 		__FPTRCAST(void (*)(struct proc *, struct proc *), *hd->hk_fn)
337 		    (p2, p1);
338 	}
339 	rw_exit(&forkhook_lock);
340 }
341 
342 static hook_list_t critpollhook_list = LIST_HEAD_INITIALIZER(critpollhook_list);
343 
344 void *
critpollhook_establish(void (* fn)(void *),void * arg)345 critpollhook_establish(void (*fn)(void *), void *arg)
346 {
347 	return hook_establish(&critpollhook_list, NULL, fn, arg);
348 }
349 
350 void
critpollhook_disestablish(void * vhook)351 critpollhook_disestablish(void *vhook)
352 {
353 	hook_disestablish(&critpollhook_list, NULL, vhook);
354 }
355 
356 /*
357  * Run critical polling hooks.
358  */
359 void
docritpollhooks(void)360 docritpollhooks(void)
361 {
362 	struct hook_desc *hd;
363 
364 	LIST_FOREACH(hd, &critpollhook_list, hk_list) {
365 		(*hd->hk_fn)(hd->hk_arg);
366 	}
367 }
368 
369 /*
370  * "Power hook" types, functions, and variables.
371  * The list of power hooks is kept ordered with the last registered hook
372  * first.
373  * When running the hooks on power down the hooks are called in reverse
374  * registration order, when powering up in registration order.
375  */
376 struct powerhook_desc {
377 	TAILQ_ENTRY(powerhook_desc) sfd_list;
378 	void	(*sfd_fn)(int, void *);
379 	void	*sfd_arg;
380 	char	sfd_name[16];
381 };
382 
383 static TAILQ_HEAD(powerhook_head, powerhook_desc) powerhook_list =
384     TAILQ_HEAD_INITIALIZER(powerhook_list);
385 
386 void *
powerhook_establish(const char * name,void (* fn)(int,void *),void * arg)387 powerhook_establish(const char *name, void (*fn)(int, void *), void *arg)
388 {
389 	struct powerhook_desc *ndp;
390 
391 	ndp = (struct powerhook_desc *)
392 	    malloc(sizeof(*ndp), M_DEVBUF, M_NOWAIT);
393 	if (ndp == NULL)
394 		return (NULL);
395 
396 	ndp->sfd_fn = fn;
397 	ndp->sfd_arg = arg;
398 	strlcpy(ndp->sfd_name, name, sizeof(ndp->sfd_name));
399 	TAILQ_INSERT_HEAD(&powerhook_list, ndp, sfd_list);
400 
401 	aprint_error("%s: WARNING: powerhook_establish is deprecated\n", name);
402 	return (ndp);
403 }
404 
405 void
powerhook_disestablish(void * vhook)406 powerhook_disestablish(void *vhook)
407 {
408 #ifdef DIAGNOSTIC
409 	struct powerhook_desc *dp;
410 
411 	TAILQ_FOREACH(dp, &powerhook_list, sfd_list)
412                 if (dp == vhook)
413 			goto found;
414 	panic("powerhook_disestablish: hook %p not established", vhook);
415  found:
416 #endif
417 
418 	TAILQ_REMOVE(&powerhook_list, (struct powerhook_desc *)vhook,
419 	    sfd_list);
420 	free(vhook, M_DEVBUF);
421 }
422 
423 /*
424  * Run power hooks.
425  */
426 void
dopowerhooks(int why)427 dopowerhooks(int why)
428 {
429 	struct powerhook_desc *dp;
430 	const char *why_name;
431 	static const char * pwr_names[] = {PWR_NAMES};
432 	why_name = why < __arraycount(pwr_names) ? pwr_names[why] : "???";
433 
434 	if (why == PWR_RESUME || why == PWR_SOFTRESUME) {
435 		TAILQ_FOREACH_REVERSE(dp, &powerhook_list, powerhook_head,
436 		    sfd_list)
437 		{
438 			if (powerhook_debug)
439 				printf("dopowerhooks %s: %s (%p)\n",
440 				    why_name, dp->sfd_name, dp);
441 			(*dp->sfd_fn)(why, dp->sfd_arg);
442 		}
443 	} else {
444 		TAILQ_FOREACH(dp, &powerhook_list, sfd_list) {
445 			if (powerhook_debug)
446 				printf("dopowerhooks %s: %s (%p)\n",
447 				    why_name, dp->sfd_name, dp);
448 			(*dp->sfd_fn)(why, dp->sfd_arg);
449 		}
450 	}
451 
452 	if (powerhook_debug)
453 		printf("dopowerhooks: %s done\n", why_name);
454 }
455 
456 /*
457  * A simple linear hook.
458  */
459 
460 khook_list_t *
simplehook_create(int ipl,const char * wmsg)461 simplehook_create(int ipl, const char *wmsg)
462 {
463 	khook_list_t *l;
464 
465 	l = kmem_zalloc(sizeof(*l), KM_SLEEP);
466 
467 	mutex_init(&l->hl_lock, MUTEX_DEFAULT, ipl);
468 	strlcpy(l->hl_namebuf, wmsg, sizeof(l->hl_namebuf));
469 	cv_init(&l->hl_cv, l->hl_namebuf);
470 	LIST_INIT(&l->hl_list);
471 	l->hl_state = HKLIST_IDLE;
472 
473 	return l;
474 }
475 
476 void
simplehook_destroy(khook_list_t * l)477 simplehook_destroy(khook_list_t *l)
478 {
479 	struct hook_desc *hd;
480 
481 	KASSERT(l->hl_state == HKLIST_IDLE);
482 
483 	while ((hd = LIST_FIRST(&l->hl_list)) != NULL) {
484 		LIST_REMOVE(hd, hk_list);
485 		kmem_free(hd, sizeof(*hd));
486 	}
487 
488 	cv_destroy(&l->hl_cv);
489 	mutex_destroy(&l->hl_lock);
490 	kmem_free(l, sizeof(*l));
491 }
492 
493 int
simplehook_dohooks(khook_list_t * l)494 simplehook_dohooks(khook_list_t *l)
495 {
496 	struct hook_desc *hd, *nexthd;
497 	kmutex_t *cv_lock;
498 	void (*fn)(void *);
499 	void *arg;
500 
501 	mutex_enter(&l->hl_lock);
502 	if (l->hl_state != HKLIST_IDLE) {
503 		mutex_exit(&l->hl_lock);
504 		return EBUSY;
505 	}
506 
507 	/* stop removing hooks */
508 	l->hl_state = HKLIST_INUSE;
509 	l->hl_lwp = curlwp;
510 
511 	LIST_FOREACH(hd, &l->hl_list, hk_list) {
512 		if (hd->hk_fn == NULL)
513 			continue;
514 
515 		fn = hd->hk_fn;
516 		arg = hd->hk_arg;
517 		l->hl_active_hk = hd;
518 		l->hl_cvlock = NULL;
519 
520 		mutex_exit(&l->hl_lock);
521 
522 		/* do callback without l->hl_lock */
523 		(*fn)(arg);
524 
525 		mutex_enter(&l->hl_lock);
526 		l->hl_active_hk = NULL;
527 		cv_lock = l->hl_cvlock;
528 
529 		if (hd->hk_fn == NULL) {
530 			if (cv_lock != NULL) {
531 				mutex_exit(&l->hl_lock);
532 				mutex_enter(cv_lock);
533 			}
534 
535 			cv_broadcast(&l->hl_cv);
536 
537 			if (cv_lock != NULL) {
538 				mutex_exit(cv_lock);
539 				mutex_enter(&l->hl_lock);
540 			}
541 		}
542 	}
543 
544 	/* remove marked node while running hooks */
545 	LIST_FOREACH_SAFE(hd, &l->hl_list, hk_list, nexthd) {
546 		if (hd->hk_fn == NULL) {
547 			LIST_REMOVE(hd, hk_list);
548 			kmem_free(hd, sizeof(*hd));
549 		}
550 	}
551 
552 	l->hl_lwp = NULL;
553 	l->hl_state = HKLIST_IDLE;
554 	mutex_exit(&l->hl_lock);
555 
556 	return 0;
557 }
558 
559 khook_t *
simplehook_establish(khook_list_t * l,void (* fn)(void *),void * arg)560 simplehook_establish(khook_list_t *l, void (*fn)(void *), void *arg)
561 {
562 	struct hook_desc *hd;
563 
564 	hd = kmem_zalloc(sizeof(*hd), KM_SLEEP);
565 	hd->hk_fn = fn;
566 	hd->hk_arg = arg;
567 
568 	mutex_enter(&l->hl_lock);
569 	LIST_INSERT_HEAD(&l->hl_list, hd, hk_list);
570 	mutex_exit(&l->hl_lock);
571 
572 	return hd;
573 }
574 
575 void
simplehook_disestablish(khook_list_t * l,khook_t * hd,kmutex_t * lock)576 simplehook_disestablish(khook_list_t *l, khook_t *hd, kmutex_t *lock)
577 {
578 	struct hook_desc *hd0 __diagused;
579 	kmutex_t *cv_lock;
580 
581 	KASSERT(lock == NULL || mutex_owned(lock));
582 	mutex_enter(&l->hl_lock);
583 
584 #ifdef DIAGNOSTIC
585 	LIST_FOREACH(hd0, &l->hl_list, hk_list) {
586 		if (hd == hd0)
587 			break;
588 	}
589 
590 	if (hd0 == NULL)
591 		panic("hook_disestablish: hook %p not established", hd);
592 #endif
593 
594 	/* The hook is not referred, remove immediately */
595 	if (l->hl_state == HKLIST_IDLE) {
596 		LIST_REMOVE(hd, hk_list);
597 		kmem_free(hd, sizeof(*hd));
598 		mutex_exit(&l->hl_lock);
599 		return;
600 	}
601 
602 	/* remove callback. hd will be removed in dohooks */
603 	hd->hk_fn = NULL;
604 	hd->hk_arg = NULL;
605 
606 	/* If the hook is running, wait for the completion */
607 	if (l->hl_active_hk == hd &&
608 	    l->hl_lwp != curlwp) {
609 		if (lock != NULL) {
610 			cv_lock = lock;
611 			KASSERT(l->hl_cvlock == NULL);
612 			l->hl_cvlock = lock;
613 			mutex_exit(&l->hl_lock);
614 		} else {
615 			cv_lock = &l->hl_lock;
616 		}
617 
618 		cv_wait(&l->hl_cv, cv_lock);
619 
620 		if (lock == NULL)
621 			mutex_exit(&l->hl_lock);
622 	} else {
623 		mutex_exit(&l->hl_lock);
624 	}
625 }
626 
627 bool
simplehook_has_hooks(khook_list_t * l)628 simplehook_has_hooks(khook_list_t *l)
629 {
630 	bool empty;
631 
632 	mutex_enter(&l->hl_lock);
633 	empty = LIST_EMPTY(&l->hl_list);
634 	mutex_exit(&l->hl_lock);
635 
636 	return !empty;
637 }
638