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

Unchecked PHY-read return values: EEE decisions and PHY write-backs derived from uninitialized kernel stack

Summary

Five clusters discard the return of hw->phy.ops.read_reg* and then use the destination u16, which is uninitialized stack when the read fails: e1000_set_eee_pchlan (gates 100M-EEE LPI enable on stack garbage); e1000_check_for_copper_link_ich8lan (masks/ORs stack garbage and writes it back into I217_PLL_CLOCK_GATE_REG/HV_PM_CTRL and HV_KMRN_FIFO_CTRLSTA); e1000_lv_jumbo_workaround_ich8lan (writes stack-garbage-derived PHY config under masks). On a PHY whose MDIO reads fail (failing or malicious hardware; SMBus-mode transitions), non-deterministic kernel-stack content is written into PHY analog/clock-gate/FIFO config registers and drives EEE decisions. Values never copied to userspace - no direct info leak; KMSAN-class defect with link-instability ceiling. Runs from the link watchdog/interrupt path on every link event on PCH hardware. Identical in FreeBSD HEAD - vendor-inherited.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3643 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
trace.sh β€” 1.1 KB view raw
trace.log β€” 3.2 KB view raw
guest_env.txt β€” 1.5 KB view raw
fix.diff β€” 1.7 KB view raw
verdict.json β€” 2.9 KB view raw
manifest.json β€” 931 B view raw

DF-3643 β€” unchecked PHY-read return values: decisions and PHY write-backs on uninitialized stack variables

What / where

sys/dev/netif/ig_hal/e1000_ich8lan.c β€” several PHY register reads ignore the hw->phy.ops.read_reg* return value and then use (and write back) the destination variable, which is an uninitialized stack u16 when the read fails:

site lines variable use after failed read
e1000_set_eee_pchlan 959-968 data (decl. 909, uninit) branch data & NWAY_LPAR_100TX_FD_CAPS selects whether 100M EEE LPI is enabled
e1000_check_for_copper_link_ich8lan 1617-1626 phy_reg (decl. 1552, uninit) masked/OR'd and written back to I217_PLL_CLOCK_GATE_REG / HV_PM_CTRL
same 1765-1772 phy_reg masked and written back to HV_KMRN_FIFO_CTRLSTA
e1000_lv_jumbo_workaround_ich8lan 2771-2773 phy_reg OR'd with (1<<14) and written back; final :2923 write-back re-enables Rx from the same garbage
same 2837-2859, 2896-2917 data several read-mask-write RMWs into PHY config

On read failure the PHY was not written, so the variable keeps whatever was on the kernel stack; the code then writes that garbage (partially masked) into PHY analog/config registers or bases an EEE enable decision on it.

Reachability

  • e1000_check_for_copper_link_ich8lan runs from the link-status interrupt / watchdog path on every link event on PCH hardware (mac.ops.check_for_link).
  • e1000_set_eee_pchlan runs after every link-up for 82579/I217 PHYs (e1000_ich8lan.c:1784-1788).
  • e1000_lv_jumbo_workaround_ich8lan runs on MTU changes to >1500 (if_em ioctl path).

A read failure is the precondition (PHY in reset/MDIO error/SMBus-mode transition) β€” on healthy hardware rare; on failing or malicious hardware inducible.

Impact

Uninitialized-kernel-stack-influenced PHY configuration (register write-backs of stack garbage under masks). The value is never copied to userspace, so there is no direct info leak; the outcome is a mis-programmed PHY (link instability at worst) and a KMSAN-class uninitialized-read defect. Severity: Info (hardening).

Reproduce (source-level)

sh findings/poc/DF-3643/trace.sh        # regenerates trace.log with all cited lines
git apply --check findings/poc/DF-3643/fix.diff

Runtime reproduction requires Intel PCH GbE PHY hardware whose MDIO reads can be made to fail; the audit guest has only virtio NICs (see guest_env.txt), so the defect is proven at source level; identical code exists in FreeBSD HEAD (upstream-inherited).

Expected result after fix

Failed PHY reads abort the EEE decision (goto release), return an error from the link-check path, or conservatively use 0-initialized values for RMW chains, so no decision or write-back is derived from uninitialized stack.

VERDICT.md
↓ download raw

VERDICT β€” DF-3643

Status: not_reproduced (runtime) β€” defect certain (source), upstream-inherited.

What I did

  1. Enumerated every hw->phy.ops.read_reg* call site in the file whose return value is discarded and whose destination is consumed afterwards. The five clusters above (trace.log) are the complete set; all other read sites either check the return or do not use the destination on the error path.
  2. Confirmed each destination is genuinely uninitialized at first use on the failing path (data decl. 909, phy_reg decl. 1552 / 2761) β€” these are per-call stack frames, so on repeated watchdog invocations the slot contains stale data from the previous call or arbitrary caller frames, i.e. non-deterministic kernel-stack content flows into PHY register writes.
  3. Verified the code is byte-identical in FreeBSD HEAD (upstream-inherited) and that DragonFly's copy is a faithful vendor snapshot (diff vs FreeBSD stable/12 contains no local edits to these functions).
  4. Guest verification (guest_env.txt): no Intel GbE MAC on the QEMU guest (virtio only), so the PHY paths never execute there; runtime reproduction is impossible on this rig. Deterministically forcing read_reg failures requires real (or emulated-FPGA) PHY hardware.

Impact ceiling (honest)

  • No kernel memory corruption, no userspace info leak (values are only written into PHY config registers and used in branches).
  • Worst realistic outcome: garbage analog/clock-gate/FIFO config written to the PHY while it is in an error state β€” link flaps/instability, i.e. a robustness defect more than a security boundary crossing. KMSAN would flag every site.
  • Classified Info (hardening) per the audit rubric.

Fix

fix.diff (git-apply-able, git apply --check verified): check the return of the PHY_LP_ABILITY read (EEE decision aborts to release), check the HV_KMRN_FIFO_CTRLSTA read (error returned to the link-check caller), check the PHY_REG(769,20) jumbo Rx-disable read, and zero-initialize the RMW chain locals so any remaining unchecked read degrades to defined defaults. Fix validation on hardware was not possible on this guest (fix_status: not_testable).

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

not_testable
baseline no→ patch + rebuild →patched clean

Not testable on this guest: forcing PHY read failures requires real Intel GbE PHY hardware; guest has virtio only. fix.diff verified to apply cleanly (git apply --check) and reviewed per-site.

findings/poc/DF-3643/fix.diff; VERDICT.md; guest_env.txt (hardware absent)
↓ fix.diffper-fix-DF-3643

Confirmed kernel references

Detail

Evidence (decisive lines)

trace.log (all cited sites with declarations); guest_env.txt (no e1000 hardware); fix.diff (git apply --check clean); VERDICT.md (complete site enumeration + reasoning)

PoC changes

n/a - no seeded PoC; evidence pack built from scratch as a source-level trace + upstream-identity proof

Verified recommended fix

Check read_reg return values at the PHY_LP_ABILITY, HV_KMRN_FIFO_CTRLSTA and PHY_REG(769,20) sites and abort on failure; zero-initialize the remaining RMW chain locals.

Verdict

Source-certain uninitialized-stack-use defect, runtime-untestable on this guest. Five call-site clusters discard the return of hw->phy.ops.read_reg* and then branch on / mask / write back the destination u16, which is uninitialized kernel stack when the read fails: e1000_set_eee_pchlan:959 (data, decl 909) drives the 100M-EEE LPI enable decision; e1000_check_for_copper_link_ich8lan:1617-1626 and 1765-1772 (phy_reg, decl 1552) write stack-garbage-derived values back into I217_PLL_CLOCK_GATE_REG/HV_PM_CTRL and HV_KMRN_FIFO_CTRLSTA; e1000_lv_jumbo_workaround_ich8lan:2771-2773 plus RMW chain 2837-2917 write garbage PHY config under masks. Identical in FreeBSD HEAD (vendor-inherited). Guest has no Intel GbE PHY (virtio only) so the paths never execute there. No userspace copy-out => no direct info leak; ceiling is mis-programmed PHY config (link instability) and KMSAN-class uninit reads. Severity Info (hardening).