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