EFI firmware has bugs which may mean that calling EFI runtime services will
authorkettenis <kettenis@openbsd.org>
Fri, 4 Nov 2022 16:49:31 +0000 (16:49 +0000)
committerkettenis <kettenis@openbsd.org>
Fri, 4 Nov 2022 16:49:31 +0000 (16:49 +0000)
fault because it does memory accesses outside of the regions it told us to
map.  Try to mitigate this by installing a fault handler (using the
pcb_onfault mechanism) and bail out using longjmp(9) if we encounter a
page fault while executing an EFI runtime services call.

Since some firmware bugs result in us executing code that isn't mapped,
make kpageflttrap() handle execution faults as well as data faults.

ok guenther@

sys/arch/amd64/amd64/efi_machdep.c
sys/arch/amd64/amd64/locore.S
sys/arch/amd64/amd64/trap.c

index cb00554..90716cb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: efi_machdep.c,v 1.2 2022/10/20 18:43:35 kettenis Exp $        */
+/*     $OpenBSD: efi_machdep.c,v 1.3 2022/11/04 16:49:31 kettenis Exp $        */
 
 /*
  * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
@@ -60,6 +60,11 @@ void efi_leave(struct efi_softc *);
 int    efi_gettime(struct todr_chip_handle *, struct timeval *);
 int    efi_settime(struct todr_chip_handle *, struct timeval *);
 
+label_t efi_jmpbuf;
+
+#define efi_enter_check(sc) (setjmp(&efi_jmpbuf) ? \
+    (efi_leave(sc), EFAULT) : (efi_enter(sc), 0))
+
 int
 efi_match(struct device *parent, void *match, void *aux)
 {
@@ -134,7 +139,8 @@ efi_attach(struct device *parent, struct device *self, void *aux)
        }
        efi_leave(sc);
 
-       efi_enter(sc);
+       if (efi_enter_check(sc))
+               return;
        status = sc->sc_rs->GetTime(&time, NULL);
        efi_leave(sc);
        if (status != EFI_SUCCESS)
@@ -225,6 +231,12 @@ efi_map_runtime(struct efi_softc *sc)
        }
 }
 
+void
+efi_fault(void)
+{
+       longjmp(&efi_jmpbuf);
+}
+
 void
 efi_enter(struct efi_softc *sc)
 {
@@ -233,11 +245,15 @@ efi_enter(struct efi_softc *sc)
        lcr3(sc->sc_pm->pm_pdirpa | (pmap_use_pcid ? PCID_EFI : 0));
 
        fpu_kernel_enter();
+
+       curpcb->pcb_onfault = (void *)efi_fault;
 }
 
 void
 efi_leave(struct efi_softc *sc)
 {
+       curpcb->pcb_onfault = NULL;
+
        fpu_kernel_exit();
 
        lcr3(sc->sc_cr3);
@@ -252,7 +268,8 @@ efi_gettime(struct todr_chip_handle *handle, struct timeval *tv)
        EFI_TIME time;
        EFI_STATUS status;
 
-       efi_enter(sc);
+       if (efi_enter_check(sc))
+               return EFAULT;
        status = sc->sc_rs->GetTime(&time, NULL);
        efi_leave(sc);
        if (status != EFI_SUCCESS)
@@ -296,7 +313,8 @@ efi_settime(struct todr_chip_handle *handle, struct timeval *tv)
        time.TimeZone = 0;
        time.Daylight = 0;
 
-       efi_enter(sc);
+       if (efi_enter_check(sc))
+               return EFAULT;
        status = sc->sc_rs->SetTime(&time);
        efi_leave(sc);
        if (status != EFI_SUCCESS)
index 2944720..9be5395 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: locore.S,v 1.128 2022/08/07 23:56:06 guenther Exp $   */
+/*     $OpenBSD: locore.S,v 1.129 2022/11/04 16:49:31 kettenis Exp $   */
 /*     $NetBSD: locore.S,v 1.13 2004/03/25 18:33:17 drochner Exp $     */
 
 /*
  */
 
 #include "assym.h"
+#include "efi.h"
 #include "lapic.h"
 #include "ksyms.h"
 #include "xen.h"
@@ -271,7 +272,7 @@ NENTRY(lgdt)
        lretq
 END(lgdt)
 
-#ifdef DDB
+#if defined(DDB) || NEFI > 0
 ENTRY(setjmp)
        RETGUARD_SETUP(setjmp, r11)
        /*
@@ -313,7 +314,7 @@ ENTRY(longjmp)
        ret
        lfence
 END(longjmp)
-#endif /* DDB */
+#endif /* DDB || NEFI > 0 */
 
 /*****************************************************************************/
 
index 53707fb..ffda7e2 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: trap.c,v 1.91 2022/11/02 07:20:07 guenther Exp $      */
+/*     $OpenBSD: trap.c,v 1.92 2022/11/04 16:49:31 kettenis Exp $      */
 /*     $NetBSD: trap.c,v 1.2 2003/05/04 23:51:56 fvdl Exp $    */
 
 /*-
@@ -228,7 +228,8 @@ kpageflttrap(struct trapframe *frame, uint64_t cr2)
        pcb = &p->p_addr->u_pcb;
 
        /* This will only trigger if SMEP is enabled */
-       if (cr2 <= VM_MAXUSER_ADDRESS && frame->tf_err & PGEX_I) {
+       if (pcb->pcb_onfault == NULL && cr2 <= VM_MAXUSER_ADDRESS &&
+           frame->tf_err & PGEX_I) {
                KERNEL_LOCK();
                fault("attempt to execute user address %p "
                    "in supervisor mode", (void *)cr2);