# DF-3989 VERDICT — source-level certain, runtime hardware-gated (inconclusive)

## Root cause (path:line)

`ale_rxeof` — sys/dev/netif/ale/if_ale.c:2153-2254:

```c
2204: length = ALE_RX_BYTES(le32toh(rs->length));   /* 14-bit, if_alereg.h:675:
                                                       ALE_RD_LEN_MASK 0x3FFF0000 */
2216: if ((status & (ALE_RD_CRC | ALE_RD_CODE | ... | ALE_RD_TRUNC)) != 0) {
2219:         ale_rx_update_page(sc, &rx_page, length, &prod);
2220:         continue;                              /* only error-bit filter */
2221: }
2232: m = m_devget(rs + 1, length - ETHER_CRC_LEN, 0, ifp);
```

`length` is a device-fed 14-bit field (0..16383) used with NO bound against
`sc->ale_max_frame_size` or the remaining space in the DMA'd Rx page. The
error-bit filter at :2216 is itself device-fed, so it bounds nothing against
a lying controller. `m_devget` (sys/kern/uipc_mbuf.c:2235) bcopy's
`length - 4` bytes from `rx_page->page_addr + rx_page->cons + 8` into a
fresh mbuf chain passed to `ifp->if_input` (:2249) — i.e. into BPF/sockets.

## Page geometry (if_alevar.h:110-116, if_ale.c:936-941)

- ALE_RX_PAGE_SZ = roundup(ALE_MAX_FRAMELEN=1522,32) × 128 = 196608.
- allocated page = roundup(guard + 196608, 32):
  non-jumbo (AR8113/8114) guard=1522 → **198144**; jumbo (AR8121)
  guard=8132 → **204768**.
- `rx_page->cons` is only reset when it reaches ALE_RX_PAGE_SZ
  (ale_rx_update_page :2068-2075), so the last in-page rs sits at cons =
  196576; the copy then reads up to 196576+8+16379 = **212963**:
  - non-jumbo: **14819 bytes past** the DMA allocation,
  - jumbo: **8195 bytes past**.

Adjacent kernel heap is copied into the delivered mbuf → kernel info leak
to any local BPF/raw/TCP listener (or the wire for unicast flows). Legit
frames max out at 8132 bytes (jumbo MTU 8110 + 18 + 4), so any length in
(8132..16383] is a device lie that this driver would faithfully copy.

## Why this is NOT a re-report

First pass over if_ale.c found nothing (verified: no findings rows for this
file). Sibling family findings exist for OTHER drivers (DF-3604 bce,
DF-3759 alc, DF-3933 age); this is the ale(4) instance of the same vendor
family defect, independently derived.

## Distinct from bce/alc siblings

The `length < ETHER_CRC_LEN` underflow is benign in ale because the
arithmetic stays uint32 and is passed to `int tot` in m_devget:
`tot <= 0` → the copy loop never runs → returns NULL → iqdrops drop
(if_ale.c:2233-2237). So ale has ONLY the over-read half of the family bug
(no negative-m_len panic variant).

## Verification on the guest (honest)

- trigger.c models the page geometry and copy extents; builds and runs on
  the guest (run.log, RC=0).
- Hardware-gated: guest ifconfig shows vtnet0/lo0 only; `pciconf -lv` has
  zero 0x1969 (Atheros) devices; `if_ale` is compiled into the kernel
  (kldload refuses: already in kernel) but no hw.ale sysctl exists →
  ale_attach/ale_rxeof unreachable. Status inconclusive per family
  convention (DF-3759/DF-3604).
- Guest src md5 ae824d7a4429b82f0ad92a72b7c91da8 == audited tree (env.txt).

## Threat model

Faulty or malicious AR8121/AR8113/AR8114 controller (PCIe DMA attacks,
broken firmware, QEMU pcie-passthrough of hostile hardware). Impact: kernel
heap disclosure (read-only). No write primitive from this bug alone.

## Fix

fix.diff drops the frame (error counter + page advance + continue) when
`length > sc->ale_max_frame_size || length < ETHER_HDR_LEN + ETHER_CRC_LEN`,
using the file's own error-frame idiom (:2216-2221). git-apply clean;
not runtime-validable (no Atheros NIC on the guest).

## 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.
