โฌข DragonFlyBSD Kernel Audit
DF-4653 / trigger.c
โ† back to finding โ†“ download raw
/* DF-4653 trigger โ€” puc_setup_intr publishes ihand before ihandarg:
 *                    puc_intr can dispatch the child handler with a NULL
 *                    argument (kernel NULL-deref panic in siointr).
 *
 * VULNERABILITY: sys/dev/misc/puc/puc.c
 *   puc_pci_attach() registers the shared IRQ handler very early:
 *     :229-230  bus_alloc_resource(dev, SYS_RES_IRQ, ..., RF_ACTIVE|RF_SHAREABLE)
 *     :236-238  BUS_SETUP_INTR(..., 0, puc_intr, sc, ..., NULL, NULL)
 *   so puc_intr() runs on ANY event on that (shareable) line for the whole
 *   attach sequence.  Children register later, during
 *   device_probe_and_attach() (:351) -> sio_attach() -> bus_setup_intr()
 *   (sys/dev/serial/sio/sio.c:1236, INTR_MPSAFE, siointr, com).
 *
 *   puc_setup_intr() publishes the handler in the WRONG ORDER:
 *     :691  sc->sc_ports[i].ihand = ihand;      <-- gate published FIRST
 *     :692  sc->sc_ports[i].ihandarg = arg;     <-- argument SECOND
 *
 *   while puc_intr() gates exactly on that publication:
 *     :407  if (sc->sc_ports[i].ihand != NULL && ...)
 *     :409      (sc->sc_ports[i].ihand)(sc->sc_ports[i].ihandarg);
 *
 *   An interrupt dispatched to puc_intr on another CPU (INTR_MPSAFE, no
 *   serializer โ€” puc passes NULL at :238 and KKASSERTs serializer == NULL at
 *   :693) between the two stores observes ihand != NULL with the stale
 *   (NULL, bzero'd at :214) ihandarg and calls siointr(NULL), which
 *   immediately dereferences it: sio.c:1750-1755 siointr(arg) ->
 *   siointr1((struct com_s *)arg) -> first deref at sio.c:1797+.
 *   Result: kernel NULL-deref panic (boot-time DoS).  Because the IRQ
 *   resource is RF_SHAREABLE, the triggering event can come from ANY
 *   sibling device sharing the line, not only from the puc card itself.
 *
 *   The plain stores carry no compiler barriers, so the compiler may also
 *   sink/swap them; the teardown side (:711-712) has the correct store
 *   order (gate cleared first) but is equally barrier-less (and dead in
 *   practice: no child driver in the tree implements detach).
 *
 * This model reproduces the publication-order defect as a userspace
 * interleaving of the exact kernel store/load pattern (thread A = CPU0
 * running puc_setup_intr, thread B = CPU1 running puc_intr on a shared-IRQ
 * event).  Synchronization is used only to OPEN the window each round โ€”
 * the kernel defect is precisely that none exists.  Three phases:
 *
 *   A. kernel store order, back-to-back stores   โ€” the faithful model
 *   B. kernel store order, 1-sched_yield() widened between the stores
 *        (instrumentation only; proves the ORDER is the defect)
 *   C. FIXED store order (arg first, gate last), same widening as B
 *        โ€” must yield ZERO NULL-argument dispatches
 *
 * B >> 0 hits while C == 0 hits proves the ordering defect; A > 0 shows it
 * is observable even at faithful (2-store) width.
 */
#include <stdio.h>
#include <sched.h>
#include <pthread.h>

struct port {                       /* mirrors sc_ports[i] (puc.c:113-118) */
	void (*ihand)(void *);
	void *ihandarg;
};

static struct port port;
static pthread_barrier_t bar;

static unsigned long dispatched;    /* calls of ihand()                  */
static unsigned long null_arg;      /* == kernel panic condition         */

static void siointr_model(void *arg)   /* siointr(): siointr1(deref arg) */
{
	/* struct com_s *com = arg; siointr1(com) -> com->... -> PANIC if NULL */
	if (arg == NULL)
		null_arg++;             /* in the kernel: NULL-deref right here */
}

static void reset_port(void)          /* fresh port, bzero'd at puc.c:214 */
{
	port.ihand = NULL;
	port.ihandarg = NULL;
}

static void *cpu0_kernel_order(void *unused)    /* puc_setup_intr :691-692 */
{
	int i;
	(void)unused;
	for (i = 0; i < 200000; i++) {
		reset_port();
		pthread_barrier_wait(&bar); /* IRQ live from :236 on            */
		/* ---- kernel store order, verbatim ---- */
		port.ihand = siointr_model;             /* :691 FIRST  (gate)  */
		port.ihandarg = (void *)0x1234;         /* :692 SECOND (arg)   */
		pthread_barrier_wait(&bar);
	}
	return NULL;
}

static void *cpu0_kernel_order_widened(void *unused)
{
	int i;
	(void)unused;
	for (i = 0; i < 200000; i++) {
		reset_port();
		pthread_barrier_wait(&bar);
		port.ihand = siointr_model;             /* :691 FIRST  (gate)  */
		sched_yield();                          /* instrumentation     */
		port.ihandarg = (void *)0x1234;         /* :692 SECOND (arg)   */
		pthread_barrier_wait(&bar);
	}
	return NULL;
}

static void *cpu0_fixed_order_widened(void *unused)  /* the fix.diff order */
{
	int i;
	(void)unused;
	for (i = 0; i < 200000; i++) {
		reset_port();
		pthread_barrier_wait(&bar);
		port.ihandarg = (void *)0x1234;         /* argument FIRST      */
		sched_yield();                          /* same widening       */
		port.ihand = siointr_model;             /* gate LAST           */
		pthread_barrier_wait(&bar);
	}
	return NULL;
}

static void *cpu1_puc_intr(void *unused)        /* puc_intr :406-409       */
{
	int i;
	(void)unused;
	for (i = 0; i < 200000; i++) {
		pthread_barrier_wait(&bar);
		/* ---- kernel load pattern, verbatim ---- */
		if (port.ihand != NULL) {              /* :407 gate             */
			void *a = port.ihandarg;       /* :409 load arg          */
			port.ihand(a);                 /* (ihand)(ihandarg)      */
			dispatched++;
		}
		pthread_barrier_wait(&bar);
	}
	return NULL;
}

static void run_phase(const char *name, void *(*cpu0)(void *))
{
	pthread_t a, b;

	dispatched = 0;
	null_arg = 0;
	pthread_barrier_init(&bar, NULL, 2);
	pthread_create(&a, NULL, cpu0, NULL);
	pthread_create(&b, NULL, cpu1_puc_intr, NULL);
	pthread_join(a, NULL);
	pthread_join(b, NULL);
	pthread_barrier_destroy(&bar);

	printf("%-44s dispatches=%-8lu NULL-arg dispatches=%lu%s\n", name,
	    dispatched, null_arg,
	    null_arg ? "   <-- kernel: siointr(NULL) panic" : "");
}

int main(void)
{
	printf("DF-4653 interleaving model: puc_setup_intr (puc.c:691-692) vs puc_intr (puc.c:406-409)\n\n");
	run_phase("A. kernel order, faithful 2-store window:", cpu0_kernel_order);
	run_phase("B. kernel order, widened (yield between):", cpu0_kernel_order_widened);
	run_phase("C. FIXED order (arg first), same widen :", cpu0_fixed_order_widened);
	printf("\nB>0 while C==0 proves the publication order is the defect.\n");
	return 0;
}