# DF-5649 — SRAT zero-length subtable: infinite loop in `srat_probe` (silent boot hang)

## What

`sys/platform/pc64/acpica/acpi_srat.c` — the SRAT entry walk advances by the
firmware-supplied one-byte `mem->head.Length` with **no lower bound**:

```c
for (mem = (srat_entry_t *)(srat + 1);                       /* :93 */
     (char *)mem < (char *)srat + srat->Header.Length;
     mem = (srat_entry_t *)((char *)mem + mem->head.Length)) /* :95 */
```

A crafted SRAT containing an entry with `Length == 0` (and `Type !=
ACPI_SRAT_TYPE_MEMORY_AFFINITY`) makes the increment add zero forever:
`srat_probe` spins silently at `SI_BOOT2_NUMA` (0x1a5c000) — the boot stops
between "CPU Topology: ..." (0x1a58000) and everything after, with no panic,
no message, and no watchdog. The same defect exists in the inner CPU-affinity
rescan (`acpi_srat.c:114-117`). The MADT sibling validates exactly this
(`madt_iterate_entries`, `acpi_madt.c:156-167`: `ent->Length < sizeof(*ent)`
→ reject); the SRAT parser simply never got the check.

## Rig (real kernel-boot manifestation)

Planted-ACPI chain, DF-5588/DF-5430 family vector: loader.conf
`hint.acpi.0.rsdp=0x40000000` (accepted by `sdt_probe`,
`acpi_sdt.c:71`, no validation) + QEMU
`-device loader,file=blob,addr=0x40000000,force-raw=on`. The blob carries
RSDP(rev2)→XSDT→{MADT(control content so SMP still boots), SRAT(scenario)}.
Two rig lessons cost us hours and are written down in `DECODE_LOG.md`:

1. The blob must survive until `SI_BOOT2_NUMA` — MADT rigs get away with
   free RAM because MADT is consumed at `SI_BOOT2_PRESMP`; 0x40000000
   worked reliably here, the SMAP-reserved hole at 0xbffdd000 is zeroed by
   SeaBIOS.
2. ACPICA table structs are `#pragma pack(1)` (`actbl3.h:194`) — see
   DECODE_LOG.md before crafting entries.

## Build

    python3 gen_srat.py hang /tmp/srat_hang.bin

## Run

    qemu-system-x86_64 -enable-kvm -m 4096 -smp 6 \
      -drive file=dfbsd-qemu/disk.qcow2,if=virtio,format=qcow2 \
      -device loader,file=/tmp/srat_hang.bin,addr=0x40000000,force-raw=on \
      -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \
      -display none -monitor unix:/tmp/m.sock,server,nowait \
      -serial file:run.log
    # (guest loader.conf must contain hint.acpi.0.rsdp=0x40000000)

## Expected output (see run.log / run.2.log)

    ...
    Bits within APICID: logical_CPU_bits: 0; core_bits: 3
    CPU Topology: cores_per_chip: 6; threads_per_core: 1; chips_per_package: 1;
    <serial output stops here forever — the kernel is spinning in srat_probe>

Control (scenario `control`, well-formed entries) prints
`vm_page: avg ... pages per queue` (from `vm_numa_organize_finalize`,
`vm_page.c:673`) within ~1s of the "CPU Topology" line and the boot
continues. The hang run never emits it — proof the walk never terminates.

## Proof the walker is on our bytes

While the guest was wedged, `pmemsave 1073741824 4096 wedge_page.bin`
(QEMU monitor) shows the SRAT region intact with the crafted
`00 10` (cpu entry) at +48 and the `00 00` (Type=0, Length=0) entry at +64 —
the exact bytes the infinite loop is iterating (`od` in VERDICT.md).

## Fix validation

`fix.diff` (mirror of madt_iterate_entries checks) applied to the guest's
/usr/src, kernel rebuilt (`make nativekernel`), re-run with the same blob:

    srat_probe: invalid SRAT entry len 0      <- new rejection message
    vm_page: avg 893 pages per queue, 1020 queues   <- walk terminated
    (boot proceeds; see fixed_hang.log)
