β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4622

ar5413: flat pd-gain curve makes sizeCurrVpdTable-1/-2 wrap uint32 -> VpdTable_I[ii][0xFFFFFFFF] wild read at row+0x1FFFFFFFE (+8.6 GB)

Summary

A pd-gain curve with all deltas 0 (flat - legal 4-bit values) gives span 0, so the [size-1]/[size-2] subscripts index with uint32 0xFFFFFFFF/0xFFFFFFFE - byte offsets ~8.59 GB past the last-member vpdTable_I row: direct-map garbage read or KVA-hole panic on channel set. Not necessarily malicious - a flat-calibrated legit card hits it every channel set. Harness SIGSEGV si_addr exactly equals row+0x1FFFFFFFE (asserted); clamp>=2 fix validated. Discovered live: the first harness run segfaulted here before DF-4619's overflow could manifest.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4622 Β· 12 files
FileTypeDescriptionSize
ar5413_harness.c β€” 11.9 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 99 B view raw
build.log β€” 707 B view raw
run.log β€” 2.4 KB view raw
env.txt β€” 271 B view raw
VERDICT.md β€” 2.4 KB ↓ raw
README.md β€” 1.9 KB ↓ raw
fix.diff β€” 739 B view raw
fix_build.log β€” 140 B view raw
fix_run.log β€” 1.3 KB view raw
verdict.json β€” 3.3 KB view raw

DF-4622 β€” ar5413 flat-curve path: sizeCurrVpdTable-1/-2 uint32 wrap β†’ +8.6 GB wild read

What

ar5413getGainBoundariesAndPdadcsForPowers() computes sizeCurrVpdTable = Pmax_t2[ii] - Pmin_t2[ii] (ar5413.c:501) β€” the half-dB span of the current pd gain's power curve. If the EEPROM curve is flat (all pwr_delta_t2 for the gain are 0) the span is 0; a single delta of 1 gives span 1. Then:

Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] -   /* :508 */
                       VpdTable_I[ii][sizeCurrVpdTable-2]);   /* :509 */

sizeCurrVpdTable is uint32_t: 0-1 wraps to 0xFFFFFFFF and 0-2 to 0xFFFFFFFE β†’ element offsets Γ—2 = +0x1FFFFFFFE / +0x1FFFFFFFC bytes (+8.59 GB) from the vpdTable_I row inside the heap-allocated struct ar5413State (last member, ar5413.c:55-56).

In the kernel this reads KVA ~8.6 GB above the RF private state: mapped RAM β†’ garbage Vpd_step (wrong pdadc extrapolation programmed to hardware); unmapped hole β†’ page fault β†’ panic. It also silently precedes/coexists with the DF-4619 kk overflow (this read happens inside the same gain loop).

A flat curve is not even necessarily malicious β€” a marginally-programmed legit card with a single flat pd gain hits this on every channel set.

Reproduce

cd findings/poc/DF-4622
./build.sh && ./ar5413_harness 3

Expected decisive lines (run.log):

REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892
  (uint32 index wrap: element index 0xFFFFFFFF x sizeof(uint16_t)=2 => +0x1FFFFFFFE bytes = +8.59 GB)
MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)

The asserted equality si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE pins the fault to exactly this code path.

Fix validation

fix.diff clamps sizeCurrVpdTable to β‰₯2 before the reads. Patched run: mode 3 completes with no SIGSEGV (clamp holds; residual in-row stale values are memory-safe). See fix_run.log.

VERDICT.md
↓ download raw

DF-4622 VERDICT

Finding

Vpd_step = (uint16_t)(VpdTable_I[ii][sizeCurrVpdTable-1] - VpdTable_I[ii][sizeCurrVpdTable-2]); at ar5413.c:508-509 inside ar5413getGainBoundariesAndPdadcsForPowers. sizeCurrVpdTable (uint32_t, :423/:501) is the current pd gain's power span in half-dB. A flat EEPROM curve (all pwr_delta_t2 0 for the gain β€” legal 4-bit values from ar2413ReadCalDataset, ah_eeprom_v3.c:449-452) gives span 0; 0-1/0-2 wrap to 0xFFFFFFFF/0xFFFFFFFE β†’ row-relative byte offsets +0x1FFFFFFFE/+0x1FFFFFFFC β†’ wild read ~8.6 GB above vpdTable_I (last member of the heap struct ar5413State, ar5413.c:55-56 β†’ reads past the allocation, far past).

Reproduction (guest)

Mode 3 of the verbatim-extraction harness: 4 gains, all curves flat (pwr_t4 = {124,124,124,124}). First gain hits :508 with span 0:

REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892
MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)

0x200402892 == &vpdTable_I[0][0] (0x402894) + 0x1FFFFFFFE β€” exact address arithmetic asserted in the SIGSEGV handler. (Userspace faults; in-kernel the direct map usually makes it a garbage read instead, or a panic on a KVA hole.)

Impact

Crafted (or merely flat-calibrated) EEPROM β†’ wild kernel read on every channel set: panic-class DoS when the wrapped KVA is unmapped, else garbage Vpd_step β†’ wrong pdadc extrapolation β†’ radio misprogramming. Same hardware-gated threat model as DF-1766/DF-4619/DF-4620. No exfiltration channel (value only feeds extrapolation arithmetic and PHY register writes).

Fix validation

fix.diff clamps sizeCurrVpdTable to β‰₯2 before the :508-509 reads (clamp comment documents the residual in-row stale-value read for span<2 β€” memory-safe, correctness only). Patched harness mode 3 completes with no SIGSEGV; values derive from in-row data. See fix_run.log --- mode 3.

Note

Distinct trigger and distinct guard from DF-4620 (:528/:532, zero-gain backfill) though the same uint32-underflow class; distinct from known DF-1766 (numChannels==0 in GetLowerUpperIndex).

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff clamps sizeCurrVpdTable to >=2 before the [size-1]/[size-2] reads; baseline faults at the exact wrapped address, patched mode 3 completes with no SIGSEGV (residual stale in-row reads are memory-safe, correctness-only).

fix_run.log '--- mode 3' section (patched): full mode-3 output with no 'REPRODUCED: SIGSEGV' line and normal termination.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 x86_64 (harness-level validation of patched function bodies; kernel-build validation hardware-gated)

Confirmed kernel references

Detail

Exploit chain

flat EEPROM power curve (deltas 0) -> sizeCurrVpdTable=0 at ar5413.c:501 -> :508-509 uint32 index wrap -> kernel read at vpdTable_I row + 0x1FFFFFFFE (past the ar5413State allocation, ~8.6GB away) -> panic (unmapped KVA) or garbage Vpd_step -> wrong pdadc extrapolation programmed to PHY. No exfil; no uid0 route (hardware-gated).

Evidence (decisive lines)

run.log mode 3 lines: 'REPRODUCED: SIGSEGV si_addr=0x200402892, expected wild address 0x200402892' and 'MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)'; fix_run.log '--- mode 3' shows patched build completing with no fault.

PoC changes

no seed PoC; discovered while isolating DF-4619 (the first harness run segfaulted here before the kk overflow could manifest); mode 3 = flat-curve dataset + SIGSEGV handler asserting the exact expected wrapped address

Verified recommended fix

Clamp sizeCurrVpdTable to >= 2 before the VpdTable_I[ii][sizeCurrVpdTable-1/-2] reads (see fix.diff).

Verdict

REPRODUCED on the DragonFly guest (verbatim-extraction harness, mode 3): with an EEPROM-shaped dataset of flat pd-gain curves (all deltas 0 β€” legal 4-bit EEPROM values), sizeCurrVpdTable==0 makes ar5413.c:508-509 index VpdTable_I[ii][(uint32)0-1] -> element 0xFFFFFFFF -> read at row+0x1FFFFFFFE bytes (+8.59 GB). SIGSEGV handler asserts si_addr == &vpdTable_I[0][0] + 0x1FFFFFFFE exactly (0x200402892) β€” the fault is pinned to this line. In-kernel: direct-map garbage read or KVA-hole panic on every channel set with such a curve (crafted EEPROM, or a flat-calibrated legit card). Fix validated: clamping sizeCurrVpdTable>=2 removes the fault; patched mode 3 runs to completion.