โฌข DragonFlyBSD Kernel Audit
DF-5538 / df5538.c
โ† back to finding โ†“ download raw
/*
 * DF-5538 PoC โ€” vkernel64 fuwordadd32()/swapu32() page-tail straddle guard
 * over-rejects: sys/platform/vkernel64/platform/copyio.c:229 (fuwordadd32)
 * and :165 (swapu32) test `PAGE_SIZE - (p & PAGE_MASK) < sizeof(uint64_t)`
 * for 32-bit operations. A 4-byte word at page offset 0xFFC has exactly
 * 4 bytes of room (fits entirely in-page), but 4 < 8 makes both functions
 * bail out with the fault sentinel -1 even though the page is mapped.
 *
 * Live caller: kern_umtx.c umtx_sleep() reads the user word with
 * `fuwordadd32(uptr, 0)` (sys/kern/kern_umtx.c:146). On native pc64 the
 * word at 0xFFC is read with a real `lock xaddl` + onfault and works.
 * In a vkernel the kernel's view of that word is permanently -1:
 *
 *   B) umtx_sleep(&w_at_0xFFC, real_value, t)  -> EBUSY immediately,
 *      even when the word *equals* real_value (should sleep ~t, rc=0)
 *   C) umtx_sleep(&w_at_0xFFC, -1, t) with word=0x41 -> sleeps the full
 *      timeout and returns 0 even though the word is NOT -1 (should be
 *      instant EBUSY)
 *
 * Run the same binary on the native host kernel (correct reference) and
 * inside a vkernel (broken). No privileges required.
 */
#include <sys/param.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

static double
now(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return ((double)tv.tv_sec + (double)tv.tv_usec / 1e6);
}

/*
 * DF umtx_sleep(2) returns -1/EAGAIN after sleeping out its timeout, and
 * -1/EBUSY instantly when the word mismatches. The semantic under test is
 * whether the kernel actually saw the user word, i.e. whether it slept.
 */
static int
probe(int *w, int value, int timeout_us, int *rcp, int *errp, double *dp)
{
	double t0, t1;
	int rc, saved;

	errno = 0;
	t0 = now();
	rc = umtx_sleep(w, value, timeout_us);
	saved = errno;
	t1 = now();
	*rcp = rc;
	*errp = saved;
	*dp = t1 - t0;
	return ((t1 - t0) > (double)timeout_us / 1000000.0 * 0.6);
}

int
main(void)
{
	char *pages;
	int *w0, *wt;
	int rc, err, slept, broken = 0;
	double d;

	pages = mmap(NULL, 3 * PAGE_SIZE, PROT_READ | PROT_WRITE,
		     MAP_ANON | MAP_PRIVATE, -1, 0);
	if (pages == MAP_FAILED) {
		perror("mmap");
		return (2);
	}
	memset(pages, 0, 3 * PAGE_SIZE);
	w0 = (int *)(pages + PAGE_SIZE);			/* page start  */
	wt = (int *)(pages + 2 * PAGE_SIZE - 4);		/* page 0xFFC  */

	printf("DF5538 pid=%d page=%p w0=%p(off 0x000) wt=%p(off 0x%03lx)\n",
	       getpid(), pages + PAGE_SIZE, (void *)w0, (void *)wt,
	       (unsigned long)((char *)wt - (pages + PAGE_SIZE)));

	*w0 = 0x41;
	*wt = 0x41;

	/* A: control at page offset 0x000 โ€” must sleep the full timeout */
	slept = probe(w0, 0x41, 1500000, &rc, &err, &d);
	printf("A control off0x000 umtx_sleep(0x41,1.5s): rc=%d errno=%d(%s) "
	       "elapsed=%.3f -> %s\n", rc, err, strerror(err), d,
	       slept ? "OK(slept)" : "UNEXPECTED");
	if (!slept)
		broken |= 4;	/* control broken too: environment problem */

	/* B: word at page offset 0xFFC, matching value โ€” must sleep */
	slept = probe(wt, 0x41, 1500000, &rc, &err, &d);
	printf("B tail    off0xFFC umtx_sleep(0x41,1.5s): rc=%d errno=%d(%s) "
	       "elapsed=%.3f -> %s\n", rc, err, strerror(err), d,
	       slept ? "OK(slept)" : "BROKEN(kernel never sees the word)");
	if (!slept)
		broken |= 1;

	/* C: word at 0xFFC holds 0x41 but ask for -1 โ€” must be instant EBUSY */
	slept = probe(wt, -1, 300000, &rc, &err, &d);
	printf("C tail    off0xFFC umtx_sleep(-1,0.3s):   rc=%d errno=%d(%s) "
	       "elapsed=%.3f -> %s\n", rc, err, strerror(err), d,
	       slept ? "BROKEN(slept on phantom -1)" :
		       "OK(instant EBUSY)");
	if (slept)
		broken |= 2;

	printf("DF5538 RESULT: %s (mask=%d)\n",
	       broken ? "BROKEN" : "OK", broken);
	return (broken ? 1 : 0);
}