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

phm_find_closest_vddci has no empty-table guard β€” count==0 indexes entries[0xFFFFFFFF]: wild kernel read ~32 GiB past the heap object (panic)

Summary

The only family member without a count!=0 guard: with count==0 the fallback reads entries[i-1] with uint32 i==0 - byte offset 0x7FFFFFFF8, a wild read faulting on any real mapping. count==0 reachable from a crafted VBIOS (the GPIO LUT path accepts ucGpioEntryNum==0 and the SMC populators invoke the lookup for legal vddci==0 entries). Harness: safe mode prints the exact offset; deref mode SIGSEGVs the verbatim function; patched twin clean. Guard-like-siblings fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4666 Β· 11 files
FileTypeDescriptionSize
find_closest_vddci_zero.c β€” 4.2 KB view raw
find_closest_vddci_zero_patched.c β€” 1.5 KB view raw
build.sh β€” 211 B view raw
run.sh β€” 90 B view raw
build.log β€” 0 B ↓ download
run.log β€” 1.0 KB view raw
env.txt β€” 188 B view raw
VERDICT.md β€” 2.6 KB ↓ raw
fix.diff β€” 772 B view raw
manifest.json β€” 1011 B view raw
verdict.json β€” 3.6 KB view raw
VERDICT.md
↓ download raw

DF-4666 β€” VERDICT

Status: reproduced (harness: verbatim function, 1:1 struct layout; run on the DragonFly 6.5-DEVELOPMENT guest).

Observations (run.log)

  1. [safe] With count = 0 (the crafted-VBIOS state produced by a GPIO VDDCI LUT with ucGpioEntryNum == 0), the verbatim fallback computes &entries[(uint32_t)0 - 1] β†’ byte offset 0x7FFFFFFF8 (34,359,738,360 β‰ˆ 32 GiB) past entries β€” far outside any possible mapping of the struct smu7_hwmgr heap object.
  2. [deref] Actually invoking the verbatim function with count==0 dies SIGSEGV (exit 139) β€” the read address is unmapped. In the kernel this is a page fault in phm_find_closest_vddci during SMC-table population at driver load β†’ panic.
  3. [normal] Control run (count=4) shows the populated path behaves correctly β€” the harness compiles the function faithfully.
  4. [patched] Twin with the count == 0 guard returns the caller's vddci with no wild access; the count=4 path is unchanged β€” fix validated.

Why it is reachable

atomctrl_get_voltage_table_v3 (ppatomctrl.c:546-563) accepts ucGpioEntryNum == 0 (assert is only <= 32) and returns success with count = 0; smu7_construct_voltage_tables (smu7_hwmgr.c:276-282) accepts it for vddci_control == BY_GPIO; nothing downstream rejects an empty table (the trim at smu7_hwmgr.c:337-342 only handles too-large). The polaris10 / vegam / fiji SMC populators then call phm_find_closest_vddci whenever a dep entry has vddci == 0 (legal VBIOS field value). Threat model: crafted VBIOS / VFIO / root sysfs pp_table β€” same as the rest of the powerplay family.

Ceiling

Wild read at a fixed huge offset β†’ deterministic panic (DoS); no disclosure or write primitive. Severity Medium, consistent with sibling DF-1798 (Medium, OOB read via unvalidated vddInd).

Kernel references

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

Patched twin with the count==0 / >32 guard returns the caller's vddci for the empty table and still returns entries[3].value for the populated control β€” wild access eliminated, behavior preserved. Full kernel rebuild not performed (no AMD GPU on the audit guest; verbatim-function harness is the faithful validation surface).

findings/poc/DF-4666/run.log '[patched]' section: 'FIX VALIDATED: empty table no longer indexes entries[0xFFFFFFFF]'.
↓ fix.diffper-fix-DF-4666

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS / VFIO / root sysfs pp_table with a VDDCI GPIO LUT of zero entries (and any dep entry with vddci==0) -> vddci_voltage_table.count=0 survives construction -> phm_find_closest_vddci -> kernel read at backend+0x7FFFFFFF8 -> page fault -> panic (DoS). Read-only primitive; no disclosure or write.

Evidence (decisive lines)

findings/poc/DF-4666/run.log: '[safe] effective byte offset = 34359738360 (0x7fffffff8)' + 'WILD-READ CONFIRMED'; '[deref] ... Segmentation fault ... exit status: 139'; '[normal] returns entries[3].value = 830'; '[patched] FIX VALIDATED'.

PoC changes

Seed harness authored fresh (no prior seed): verbatim smu_helper.c:426-437 body plus 1:1 pp_atomctrl_voltage_table layout from ppatomctrl.h; three modes (safe address math, live deref, populated control) plus patched twin.

Verified recommended fix

Add the same empty-table guard as phm_get_voltage_index/phm_get_voltage_id to phm_find_closest_vddci (return caller vddci on NULL/count==0/count>32).

Verdict

Harness-verified on the DragonFly 6.5-DEVELOPMENT guest with the verbatim kernel function and 1:1 struct layout: phm_find_closest_vddci (smu_helper.c:426-437) is the only member of its lookup family with no empty-table guard; with count==0 the fallback 'return vddci_table->entries[i-1].value' indexes entries[0xFFFFFFFF], reading at +0x7FFFFFFF8 (34,359,738,360 bytes ~ 32 GiB) past the heap object. The deref run of the verbatim function dies SIGSEGV(139); in-kernel this is a guaranteed page fault / panic during SMC table population at driver load. count==0 is reachable from a crafted VBIOS: atomctrl_get_voltage_table_v3 (ppatomctrl.c:546-563) accepts ucGpioEntryNum==0 (assert is only <=32) and returns success, smu7_construct_voltage_tables accepts it for BY_GPIO vddci_control, and polaris10/vegam/fiji populators invoke the lookup whenever a dep-table entry has vddci==0. Patched twin (count==0 guard) returns the caller's vddci with no wild access; normal path unchanged.