]> xenbits.xensource.com Git - people/royger/freebsd.git/commitdiff
kern linker: do not allow more than one kldload and kldunload syscalls simultaneously
authorKonstantin Belousov <kib@FreeBSD.org>
Thu, 20 May 2021 14:50:43 +0000 (17:50 +0300)
committerKonstantin Belousov <kib@FreeBSD.org>
Mon, 31 May 2021 15:09:22 +0000 (18:09 +0300)
kld_sx is dropped e.g. for executing sysinits, which allows user
to initiate kldunload while module is not yet fully initialized.

Reviewed by: markj
Differential revision: https://reviews.freebsd.org/D30456
Sponsored by: The FreeBSD Foundation
MFC after: 1 week

sys/kern/kern_linker.c
sys/sys/linker.h

index d30db0fb2dbab124df5d9a950eb23c3c8c96904f..bb17c020e98a868d845478d710315cd6ec7c48b8 100644 (file)
@@ -106,6 +106,7 @@ MALLOC_DEFINE(M_LINKER, "linker", "kernel linker");
 linker_file_t linker_kernel_file;
 
 static struct sx kld_sx;       /* kernel linker lock */
+static bool kld_busy;
 
 /*
  * Load counter used by clients to determine if a linker file has been
@@ -1050,6 +1051,49 @@ linker_search_symbol_name(caddr_t value, char *buf, u_int buflen,
            M_WAITOK));
 }
 
+int
+linker_kldload_busy(int flags)
+{
+       int error;
+
+       MPASS((flags & ~(LINKER_UB_UNLOCK | LINKER_UB_LOCKED |
+           LINKER_UB_PCATCH)) == 0);
+       if ((flags & LINKER_UB_LOCKED) != 0)
+               sx_assert(&kld_sx, SA_XLOCKED);
+
+       if ((flags & LINKER_UB_LOCKED) == 0)
+               sx_xlock(&kld_sx);
+       while (kld_busy) {
+               error = sx_sleep(&kld_busy, &kld_sx,
+                   (flags & LINKER_UB_PCATCH) != 0 ? PCATCH : 0,
+                   "kldbusy", 0);
+               if (error != 0) {
+                       if ((flags & LINKER_UB_UNLOCK) != 0)
+                               sx_xunlock(&kld_sx);
+                       return (error);
+               }
+       }
+       kld_busy = true;
+       if ((flags & LINKER_UB_UNLOCK) != 0)
+               sx_xunlock(&kld_sx);
+       return (0);
+}
+
+void
+linker_kldload_unbusy(int flags)
+{
+       MPASS((flags & ~LINKER_UB_LOCKED) == 0);
+       if ((flags & LINKER_UB_LOCKED) != 0)
+               sx_assert(&kld_sx, SA_XLOCKED);
+
+       if ((flags & LINKER_UB_LOCKED) == 0)
+               sx_xlock(&kld_sx);
+       MPASS(kld_busy);
+       kld_busy = false;
+       wakeup(&kld_busy);
+       sx_xunlock(&kld_sx);
+}
+
 /*
  * Syscalls.
  */
@@ -1066,12 +1110,6 @@ kern_kldload(struct thread *td, const char *file, int *fileid)
        if ((error = priv_check(td, PRIV_KLD_LOAD)) != 0)
                return (error);
 
-       /*
-        * It is possible that kldloaded module will attach a new ifnet,
-        * so vnet context must be set when this ocurs.
-        */
-       CURVNET_SET(TD_TO_VNET(td));
-
        /*
         * If file does not contain a qualified name or any dot in it
         * (kldname.ko, or kldname.ver.ko) treat it as an interface
@@ -1085,19 +1123,27 @@ kern_kldload(struct thread *td, const char *file, int *fileid)
                modname = file;
        }
 
-       sx_xlock(&kld_sx);
-       error = linker_load_module(kldname, modname, NULL, NULL, &lf);
-       if (error) {
+       error = linker_kldload_busy(LINKER_UB_PCATCH);
+       if (error != 0) {
                sx_xunlock(&kld_sx);
-               goto done;
+               return (error);
        }
-       lf->userrefs++;
-       if (fileid != NULL)
-               *fileid = lf->id;
-       sx_xunlock(&kld_sx);
 
-done:
+       /*
+        * It is possible that kldloaded module will attach a new ifnet,
+        * so vnet context must be set when this ocurs.
+        */
+       CURVNET_SET(TD_TO_VNET(td));
+
+       error = linker_load_module(kldname, modname, NULL, NULL, &lf);
        CURVNET_RESTORE();
+
+       if (error == 0) {
+               lf->userrefs++;
+               if (fileid != NULL)
+                       *fileid = lf->id;
+       }
+       linker_kldload_unbusy(LINKER_UB_LOCKED);
        return (error);
 }
 
@@ -1132,8 +1178,13 @@ kern_kldunload(struct thread *td, int fileid, int flags)
        if ((error = priv_check(td, PRIV_KLD_UNLOAD)) != 0)
                return (error);
 
+       error = linker_kldload_busy(LINKER_UB_PCATCH);
+       if (error != 0) {
+               sx_xunlock(&kld_sx);
+               return (error);
+       }
+
        CURVNET_SET(TD_TO_VNET(td));
-       sx_xlock(&kld_sx);
        lf = linker_find_file_by_id(fileid);
        if (lf) {
                KLD_DPF(FILE, ("kldunload: lf->userrefs=%d\n", lf->userrefs));
@@ -1153,9 +1204,8 @@ kern_kldunload(struct thread *td, int fileid, int flags)
                }
        } else
                error = ENOENT;
-       sx_xunlock(&kld_sx);
-
        CURVNET_RESTORE();
+       linker_kldload_unbusy(LINKER_UB_LOCKED);
        return (error);
 }
 
index 73eea35dab554dc359e7cfa9f18da9a178231cd6..a3d5eb0e72cf169c5007c2b65b2fa7d92bf0f32c 100644 (file)
@@ -196,6 +196,15 @@ int linker_search_symbol_name(caddr_t value, char *buf, u_int buflen,
 /* HWPMC helper */
 void *linker_hwpmc_list_objects(void);
 
+/* kldload/kldunload syscalls blocking */
+#define        LINKER_UB_UNLOCK        0x0001  /* busy: unlock kld_sx locked on
+                                          return */
+#define        LINKER_UB_LOCKED        0x0002  /* busy/unbusy: kld_sx locked on
+                                          entry */
+#define        LINKER_UB_PCATCH        0x0004  /* busy: sleep interruptible */
+int linker_kldload_busy(int flags);
+void linker_kldload_unbusy(int flags);
+
 #endif /* _KERNEL */
 
 /*