# DF-5432 — VERDICT

## 1. Reproduced? YES — real kernel-boot adoption of attacker-chosen RAM as the IOAPIC, with kernel writes to that page

| run | kernel | observation |
|-----|--------|-------------|
| run.log    | B (Jun 29 clean-install) | `IOAPIC: idx 0, apic id 2, gsi base 0, npin 241`; monitor: `0x40001000: 0x0000018d` |
| run.3.log  | A (Jul 2 with-src) — fix baseline | identical adoption + identical `0x18d` select-write |

Decisive lines (run.log:87-88 / run.3.log:87-88):

```
ACPI MADT: IOAPIC addr 0x40001000, apic id 0, gsi base 0
IOAPIC: idx 0, apic id 2, gsi base 0, npin 241
```

`0x40001000` is ordinary guest RAM planted via QEMU's loader device with a
fake register file (IOWIN `0x00F00000` → `npin 241`; the real QEMU IOAPIC
reports 24). The kernel:

1. mapped the page: `madt_ioapic_enum_callback` (acpi_madt.c:733) →
   `ioapic_map` (ioapic.c:603-607, `pmap_mapdev_uncacheable(pa, PAGE_SIZE)`,
   only `KKASSERT(pa < 4GB)`);
2. read configuration from it (`ioapic_read`, ioapic_ipl.s:117-120 — a
   32-bit store of the register index at +0x00, load at +0x10) and adopted
   `npin 241`;
3. **wrote** to it — `ioapic_set_apic_id` rewrote the "APIC ID" through the
   page (read-back matched because RAM echoes writes: ioapic.c:312-332),
   and `ioapic_pin_prog` (ioapic.c:541-543) programmed all 241 redirection
   entries (`ioapic_write`, ioapic_ipl.s:123-131: select store at +0x00,
   data store at +0x10).

QEMU-monitor physical dump (monitor_baseline.txt) after the run:

```
0000000040001000: 0x0000018d ...     <- kernel's register-select WRITE (blob planted 0)
0000000040001010: 0x00f00000 ...     <- window; final written value coincides with the
                                        planted word because the redirection target
                                        preserves reserved bits read back from the page
```

The value written to the window is partially attacker-influenced
(vector byte = `IDT_OFFSET + irq`, flags from trigger/polarity, destination
byte = APIC ID << 24); the select-index write at +0x00 is fully kernel-chosen
but proves the arbitrary-physical-page write primitive. This is the IOAPIC
twin of DF-5365 (lapic_map, Medium): same firmware-fed precondition, same
arbitrary-phys map + MMIO write class; DF-5365 at least had IA32_APICBASE to
cross-check against, while the IOAPIC address is adopted with literally zero
validation (probe rejects only `Address == 0`, acpi_madt.c:619-622).

The machine continued booting (virtio on MSI-X; serial console polling) —
a RAM page was the system's interrupt router until the rig artifact stopped
it (see env.txt: late AcpiTbLoadNamespace DDB stop also present in the
healthy control boot, strictly after the stage under test).

## 2. Fix — VALIDATED on a single-fix kernel

`fix.diff`: probe-side rejection of IOAPIC addresses that fall inside any
`phys_avail[]` RAM segment (`vm/pmap.h:149` — populated by MD boot long
before SI_BOOT2_PRESMP; device MMIO never lives in RAM).

Baseline vs patched, identical blob (`madt_fake_ioapic.bin`), same
loader.conf hint, kernel A lineage:

| | baseline (run.3.log) | patched (run.fixed.log) |
|---|---|---|
| probe | accepts (no address check) | `madt_ioapic_probe: IOAPIC address 0x40001000 in system RAM, rejecting MADT` (run.fixed.log:87) |
| ioapic source | RAM page, `npin 241` | mptable fallback: real IOAPIC `npin 24` (run.fixed.log:88-90) |
| phys 0x40001000 after boot | `0x0000018d` (kernel wrote) | **all zeros except planted 0x00f00000 — kernel never touched the page** (monitor_fixed.txt) |

Build: guest /usr/src + fix.diff (`git apply`), `make -j6 nativekernel
KERNCONF=X86_64_GENERIC` → RC=0 (no warnings/errors; full log lost with the
post-validation disk revert — see build_summary.txt for the captured tail),
`make installkernel` RC=0, clean reboot into patched kernel. Behavior change
is exactly the intended one and nothing else regressed (LAPIC stage
identical: `madt_lapic_probe: lapic_count=2`, `ACPI CPUS = 2`).

## 3. Threat model

Firmware/bootloader-controlled MADT (demonstrated via the unvalidated
`hint.acpi.0.rsdp` kenv vector + QEMU loader device). No unprivileged
in-guest path. Impact ceiling with that control: kernel MMIO writes
(partially controlled 32-bit values, fixed +0x00/+0x10 offsets) and MMIO
reads of arbitrary physical pages below 4GB at early boot — same class and
bar as accepted DF-5365 (Medium, memcorrupt).

## 4. Exploit chain (honest)

firmware-controlled table → arbitrary-phys page mapped uncached →
repeated 32-bit kernel stores at page+0/+0x10 with semi-controlled data.
Turning that into code execution requires the firmware attacker to also
survive early boot and aim the page at security-relevant state (e.g. a page
table / kernel .text in low phys); at that point the attacker already owns
pre-boot, which is why this is filed Medium (matching DF-5365) and not
higher. Not pursued to uid=0 — no unprivileged-user trigger exists.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
