โฌข DragonFlyBSD Kernel Audit
DF-3582 / trigger_df3582.c
โ† back to finding โ†“ download raw
/*
 * DF-3582 trigger logic โ€” faithful extraction of the kernel write performed by
 * sys/dev/disk/isp/isp.c isp_parse_status() RQCS_RESET_OCCURRED handler:
 *
 *      case RQCS_RESET_OCCURRED:
 *      {
 *              int chan;
 *              isp_xs_prt(isp, xs, ISP_LOGWARN, "Bus Reset destroyed command");
 *              for (chan = 0; chan < isp->isp_nchan; chan++) {
 *                      FCPARAM(isp, chan)->sendmarker = 1;      <-- THE BUG
 *              }
 *              ...
 *
 * On a SCSI card isp->isp_param is kmalloc(nchan * sizeof(sdparam)) bytes
 * (isp_pci.c:827-832), but FCPARAM() casts it to (fcparam *). sendmarker is
 * bit 3 of the first word of fcparam, so chan=1 writes at
 * isp_param + sizeof(fcparam) == isp_param + 21576 โ€” far outside the
 * 2*204 = 408 byte dual-bus allocation.
 *
 * This harness places a 408-byte buffer (exactly what a dual-bus SCSI card
 * gets) immediately before a PROT_NONE guard page and executes the identical
 * bitfield read-modify-write sequence. chan=0 succeeds; chan=1 faults at
 * +21576 โ€” proving the displacement arithmetic with the real struct
 * definitions taken from the kernel tree.
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

typedef void *XS_T;
typedef uint64_t XS_DMA_ADDR_T;
typedef struct { uint64_t t; } NANOTIME_T;
typedef struct { uint16_t param[16]; } mbreg_t;
#define MAILBOX_STORAGE 16
struct isposinfo { char pad[128]; };
#define ISP_KERNEL 1
#include <dev/disk/isp/isp_stds.h>
#include <dev/disk/isp/ispmbox.h>
#include <dev/disk/isp/ispvar.h>

static sigjmp_buf jb;
static volatile sig_atomic_t got;
static char faultbuf[64];

static void
handler(int sig, siginfo_t *si, void *uc)
{
	snprintf(faultbuf, sizeof (faultbuf), "SIGSEGV/%s", sig == SIGBUS ? "BUS" : "SEGV");
	got = (sig_atomic_t)((uint8_t *)si->si_addr - (uint8_t *)0);
	siglongjmp(jb, 1);
}

int
main(void)
{
	size_t pagesz = 4096, allocsz = 2 * sizeof(sdparam);	/* dual-bus SCSI */
	uint8_t *base, *map;
	struct sigaction sa;
	int chan;

	printf("sizeof(sdparam) = %zu\n", sizeof(sdparam));
	printf("sizeof(fcparam) = %zu\n", sizeof(fcparam));
	printf("SCSI dual-bus isp_param allocation = %zu bytes (isp_pci.c:827-832)\n",
	    allocsz);
	printf("FCPARAM(isp,1)->sendmarker write offset = +0x%zx (%zu) bytes\n",
	    sizeof(fcparam), sizeof(fcparam));
	printf("=> %zd bytes PAST the end of the allocation\n\n",
	    (ssize_t)sizeof(fcparam) - (ssize_t)allocsz);

	/*
	 * Map a large PROT_NONE reserve, then make exactly one page RW at its
	 * start. The allocation stand-in lives in that page; ANY access beyond
	 * it faults deterministically (like an unmapped/kmem_guard hole).
	 */
	size_t mapsz = 1 << 20;
	map = mmap(NULL, mapsz, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
	if (map == MAP_FAILED) { perror("mmap"); return (1); }
	if (mprotect(map, pagesz, PROT_READ|PROT_WRITE) != 0) {
		perror("mprotect"); return (1);
	}
	base = map;			/* kmalloc(2 * sizeof(sdparam)) stand-in */
	memset(base, 0, allocsz);

	memset(&sa, 0, sizeof (sa));
	sa.sa_sigaction = handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	/* Exactly what the kernel loop does (isp.c:6109):
	 *   FCPARAM(isp, chan)->sendmarker = 1;
	 * sendmarker is a 1-bit field in the first uint32_t of fcparam.
	 */
	for (chan = 0; chan < 2; chan++) {
		fcparam *fcp = &((fcparam *)(void *)base)[chan];
		if (sigsetjmp(jb, 1) != 0) {
			printf("chan=%d: FAULT (%s) touching %p => offset %zu "
			    "from allocation start (%zd past end of %zu-byte alloc)\n",
			    chan, faultbuf,
			    (void *)((uint8_t *)base + sizeof(fcparam) * (size_t)chan),
			    sizeof(fcparam) * (size_t)chan,
			    (ssize_t)(sizeof(fcparam) * (size_t)chan) - (ssize_t)allocsz,
			    allocsz);
			return (0);
		}
		fcp->sendmarker = 1;	/* the kernel's write */
		printf("chan=%d: write at +%zu OK (in-allocation)\n",
		    chan, sizeof(fcparam) * (size_t)chan);
	}
	printf("ERROR: no fault occurred\n");
	return (1);
}