โฌข DragonFlyBSD Kernel Audit
DF-5652 / elcrx.c
โ† back to finding โ†“ download raw
/* DF-5652 / DF-5653 / DF-5654 โ€” ELCR (sys/platform/pc64/icu/elcr.c) live
 * introspection + trigger-mode protocol harness (DragonFly pc64 KLD).
 *
 * Loaded as root via kldload(8) on the audit QEMU/i440fx guest
 * (X86_64_GENERIC, INVARIANTS kernel, IOAPIC MachIntrABI โ€” the ELCR
 * is a live boot-time device at ports 0x4d0/0x4d1 either way).
 *
 * Phases (destructive phases touch ONLY the ELCR ports and restore exactly):
 *
 *   E0: environment โ€” ncpu, ABI type, elcr_found, hardware ELCR read,
 *       shadow-vs-hardware coherence table via elcr_read_trigger(0..15).
 *
 *   E1: DF-5652 read path โ€” the bound check at elcr.c:89 is
 *       KASSERT(irq < ISA_IRQ_CNT) with NO signed lower bound and is
 *       INVARIANTS-only anyway (sys/sys/systm.h:94-122). Negative irqs
 *       are accepted even on THIS INVARIANTS kernel: elcr_read_trigger()
 *       evaluates the C-undefined shift elcr_status & (1 << irq)
 *       (elcr.c:57,90). -22 aliases bit 10 (1 << (-22 & 31) == 1 << 10
 *       on x86): trigger(-22) must equal trigger(10) โ€” self-proving.
 *       Read path never touches ports -> zero hardware risk.
 *
 *   E2: DF-5652 write path โ€” elcr_write_trigger(-27, LEVEL) passes the
 *       assert (-27 < 16), materializes 1 << -27 as 1 << 5, mutates the
 *       elcr_status shadow AND PROGRAMS THE LIVE ELCR PORT 0x4d0
 *       (elcr.c:103-117). IRQ5 is a masked, unused line on this rig;
 *       state restored exactly afterwards.
 *
 *   R:  DF-5653 โ€” elcr_write_trigger() performs an unlocked
 *       read-modify-write of the global elcr_status shadow
 *       (elcr.c:103-117; caller icu_abi_legacy_intr_config holds no
 *       lock either). Two kernel threads pinned to cpu0/cpu1 toggle
 *       irq5/irq7 (same 32-bit shadow word, same 0x4d0 byte)
 *       LEVEL<->EDGE N times each. A read-back != just-written mode
 *       means the peer's stale-word store clobbered our bit: a lost
 *       update, i.e. a silently dropped trigger-mode change.
 *
 *   D:  DF-5654 โ€” no resync path exists: elcr_status is read from the
 *       hardware exactly once (elcr_probe, elcr.c:75) and never again.
 *       Flip irq5's bit in the HARDWARE with a raw outb(0x4d0):
 *       elcr_read_trigger(5) keeps serving the stale shadow (divergence
 *       invisible to the kernel). Then call elcr_resume() (elcr.c:120,
 *       ZERO in-tree callers โ€” dead code) and watch it repair the
 *       hardware to the shadow exactly: the missing wiring, proven.
 *
 *   E9: final coherence vs the E0 baseline, restore verification,
 *       liveness (ticks advance).
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/kthread.h>
#include <machine/atomic.h>
#include <sys/machintr.h>
#include <sys/thread2.h>
#include <machine/cpufunc.h>

/* kernel symbols (all .globl; elcr_var.h decls inlined to avoid
 * machine_base include-path games in the kmod build) */
extern int			elcr_found;			/* elcr.c:59 */
extern enum intr_trigger	elcr_read_trigger(int);		/* elcr.c:85 */
extern void			elcr_write_trigger(int, enum intr_trigger); /* elcr.c:100 */
extern void			elcr_resume(void);		/* elcr.c:120 */

#define	ELCR_PORT	0x4d0				/* elcr.c:56 */
#define	IRQ_A		5				/* masked/unused on rig */
#define	IRQ_B		7				/* masked/unused on rig */
#define	RACE_ITERS	300000

static volatile int	race_done;
static volatile int	lost_a, lost_b;
static volatile int	final_a = -1, final_b = -1;
static int		race_arg_a = IRQ_A;
static int		race_arg_b = IRQ_B;

static int
read_elcr_hw(void)
{
	return (inb(ELCR_PORT) | (inb(ELCR_PORT + 1) << 8));
}

static const char *
trig_str(enum intr_trigger t)
{
	return (t == INTR_TRIGGER_LEVEL ? "L" : (t == INTR_TRIGGER_EDGE ? "E" : "?"));
}

/*
 * Race worker: toggle one irq's trigger mode RACE_ITERS times on a
 * pinned CPU. After every write, read the shadow back; a mismatch is a
 * lost update caused by the peer's unlocked stale-word store.
 * NOTE: no start barrier (v1 starved the flag-setting kldload thread on
 * cpu0 with never-yielding spin threads -> guest wedge, wedge_v1.log);
 * the 300k-iteration loops overlap naturally, and lwkt_yield() every
 * 16k iterations keeps the CPU available to the scheduler.
 */
static void
race_fn(void *arg)
{
	int irq = *(int *)arg;
	int i, mode, lost = 0;

	for (i = 0; i < RACE_ITERS; ++i) {
		if ((i & 16383) == 16384 - 1)
			lwkt_yield();		/* keep the CPU shareable */
		mode = (i & 1) ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL;
		elcr_write_trigger(irq, mode);
		if (elcr_read_trigger(irq) != mode)
			++lost;			/* our bit was clobbered */
	}

	if (irq == IRQ_A) {
		lost_a = lost;
		final_a = mode;
	} else {
		lost_b = lost;
		final_b = mode;
	}
	atomic_add_int(&race_done, 1);
	wakeup((void *)(intptr_t)&race_done);
	kthread_exit();
}

static int
elcrx_modevent(module_t mod, int type, void *data)
{
	int hw_base, hw_pre, hw_now, error;
	int b5, i, shadow_bits, waited;
	enum intr_trigger t, m5, m7;

	switch (type) {
	case MOD_LOAD:
		break;
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}

	/* ---------------- E0: environment + coherence baseline ---------- */
	hw_base = read_elcr_hw();
	shadow_bits = 0;
	for (i = 0; i < 16; ++i)
		if (elcr_read_trigger(i) == INTR_TRIGGER_LEVEL)
			shadow_bits |= 1 << i;
	kprintf("E0: ncpu=%d mycpuid=%d MachIntrABI.type=%d "
	    "(ICU=1 IOAPIC=2) elcr_found=%d\n",
	    ncpus, mycpuid, MachIntrABI.type, elcr_found);
	kprintf("E0: hw ELCR=0x%04x shadow LEVEL-bits=0x%04x coherent=%s\n",
	    hw_base, shadow_bits,
	    (hw_base & 0xffff) == shadow_bits ? "YES" : "NO");

	/* ---------------- E1: DF-5652 read path, negative irq ------------ */
	t = elcr_read_trigger(-1);		/* 1<<(-1&31)=1<<31: expect E */
	kprintf("E1: elcr_read_trigger(-1)=%s (no panic: negative irq "
	    "ACCEPTED, assert only checks irq<16)\n", trig_str(t));
	kprintf("E1: elcr_read_trigger(-22)=%s vs elcr_read_trigger(10)=%s "
	    "-> %s (1<<-22 materialized as 1<<10 on x86)\n",
	    trig_str(elcr_read_trigger(-22)),
	    trig_str(elcr_read_trigger(10)),
	    elcr_read_trigger(-22) == elcr_read_trigger(10) ?
	    "ALIAS-PROVEN" : "differs");
	kprintf("E1: hw still 0x%04x (read path touches no ports)\n",
	    read_elcr_hw());

	/* ---------------- E2: DF-5652 write path, negative irq ------------ */
	b5 = elcr_read_trigger(IRQ_A);
	hw_pre = inb(ELCR_PORT);
	elcr_write_trigger(IRQ_A - 32, INTR_TRIGGER_LEVEL);	/* -27 */
	hw_now = inb(ELCR_PORT);
	kprintf("E2: elcr_write_trigger(-27, LEVEL): hw0x4d0 0x%02x -> 0x%02x "
	    "(bit5 %s), shadow trigger(5)=%s\n",
	    hw_pre, hw_now,
	    ((hw_now >> IRQ_A) & 1) ? "SET" : "clear",
	    trig_str(elcr_read_trigger(IRQ_A)));
	elcr_write_trigger(IRQ_A, (enum intr_trigger)b5);	/* restore */
	kprintf("E2: restored: hw0x4d0=0x%02x (was 0x%02x) trigger(5)=%s "
	    "(was %s) ok=%s\n",
	    inb(ELCR_PORT), hw_pre, trig_str(elcr_read_trigger(IRQ_A)),
	    trig_str(b5),
	    (inb(ELCR_PORT) == hw_pre &&
	     elcr_read_trigger(IRQ_A) == b5) ? "YES" : "NO");

	/* ---------------- R: DF-5653 unlocked shadow RMW race ------------- */
	m5 = elcr_read_trigger(IRQ_A);
	m7 = elcr_read_trigger(IRQ_B);
	hw_pre = read_elcr_hw();
	lost_a = lost_b = 0;
	final_a = final_b = -1;
	race_done = 0;
	kprintf("R0: baseline trigger(5)=%s trigger(7)=%s hw=0x%04x "
	    "iters/thread=%d\n", trig_str(m5), trig_str(m7), hw_pre,
	    RACE_ITERS);
	error = kthread_create_cpu(race_fn, &race_arg_a, NULL, 0,
	    "elcrx_a");
	if (error == 0)
		error = kthread_create_cpu(race_fn, &race_arg_b, NULL, 1,
		    "elcrx_b");
	if (error) {
		kprintf("R: kthread_create_cpu failed: %d\n", error);
		goto phase_d;
	}
	waited = 0;
	while (race_done < 2 && waited < 120) {		/* <=120 s guard */
		tsleep((void *)(intptr_t)&race_done, 0, "elcrxw", hz);
		++waited;
	}
	kprintf("R1: done=%d waited=%ds lost_irq5=%d lost_irq7=%d "
	    "(read-back mismatches = lost trigger-mode updates)\n",
	    race_done, waited, lost_a, lost_b);
	kprintf("R1: final shadow trigger(5)=%s (last written %s) "
	    "trigger(7)=%s (last written %s)\n",
	    trig_str(elcr_read_trigger(IRQ_A)), trig_str((enum intr_trigger)final_a),
	    trig_str(elcr_read_trigger(IRQ_B)), trig_str((enum intr_trigger)final_b));
	elcr_write_trigger(IRQ_A, m5);			/* restore baseline */
	elcr_write_trigger(IRQ_B, m7);
	kprintf("R2: restored: hw=0x%04x (was 0x%04x) trigger(5)=%s "
	    "trigger(7)=%s ok=%s\n",
	    read_elcr_hw(), hw_pre, trig_str(elcr_read_trigger(IRQ_A)),
	    trig_str(elcr_read_trigger(IRQ_B)),
	    (read_elcr_hw() == hw_pre &&
	     elcr_read_trigger(IRQ_A) == m5 &&
	     elcr_read_trigger(IRQ_B) == m7) ? "YES" : "NO");

phase_d:
	/* ---------------- D: DF-5654 no-resync / dead elcr_resume --------- */
	b5 = inb(ELCR_PORT);				/* shadow==hw here */
	outb(ELCR_PORT, b5 ^ (1 << IRQ_A));		/* HW-only divergence */
	kprintf("D1: raw outb flipped hw bit5: hw0x4d0=0x%02x but "
	    "elcr_read_trigger(5)=%s -> kernel serves STALE shadow "
	    "(no resync path exists; elcr_status read once at probe)\n",
	    inb(ELCR_PORT), trig_str(elcr_read_trigger(IRQ_A)));
	elcr_resume();			/* ZERO in-tree callers โ€” dead code */
	kprintf("D2: after elcr_resume(): hw=0x%04x baseline=0x%04x "
	    "repaired=%s (the dead function is exactly the missing fix)\n",
	    read_elcr_hw(), hw_base,
	    read_elcr_hw() == hw_base ? "YES" : "NO");

	/* ---------------- E9: final state --------------------------------- */
	kprintf("E9: final hw=0x%04x baseline=0x%04x coherent=%s "
	    "ticks=%d -> guest %s\n",
	    read_elcr_hw(), hw_base,
	    read_elcr_hw() == hw_base ? "YES" : "NO", ticks,
	    read_elcr_hw() == hw_base ? "HEALTHY" : "DIRTY");
	return (0);
}

static moduledata_t elcrx_mod = { "elcrx", elcrx_modevent, NULL };
DECLARE_MODULE(elcrx, elcrx_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);