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

bge_nvram_getbyte leaks the NVRAM software-arbitration lock and leaves NVRAM access enabled on its two timeout paths (BCM5906)

Summary

bge_nvram_getbyte pairs the BGE_NVRAM_SWARB acquire with release and saves/restores BGE_NVRAM_ACCESS on the success path - unlike the bnx sibling DF-3640 which never released - but returns without cleanup on the grant-timeout path (request bit left latched) and on the read-done timeout path (arbiter still held AND BGE_NVRAMACC_ENABLE left set). Reachable only at attach time on BCM5906. After a single NVRAM timeout during attach (already-malfunctioning hardware), the flash arbiter stays held for the driver's lifetime and the NVRAM access window remains enabled, starving ASF/management firmware of NVRAM - device-side functional DoS only; no host memory-safety impact and no unprivileged trigger.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3664 Β· 11 files
FileTypeDescriptionSize
trigger.c β€” 3.2 KB view raw
build.sh β€” 107 B view raw
run.sh β€” 158 B view raw
build.log β€” 84 B view raw
run.log β€” 481 B view raw
env.txt β€” 342 B view raw
fix.diff β€” 714 B view raw
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 4.2 KB view raw

DF-3664 β€” bge_nvram_getbyte leaks NVRAM SWARB lock + access-enable on error paths

Build / Run / Expected

./build.sh          # cc -O -Wall -o trigger trigger.c   (in a DragonFly guest)
./run.sh            # runs the trigger + records the guest-side reachability proof

Expected on real BCM5906 hardware (NOT available in this environment): an NVRAM read that times out (grant timeout at if_bge.c:557 or read-done timeout at if_bge.c:574) returns while BGE_NVRAMSWARB_SET1 is still latched and BGE_NVRAMACC_ENABLE is still set; the flash arbiter stays held by the driver for its lifetime, starving ASF/management firmware of NVRAM access. Device-side functional DoS only β€” no host memory-safety impact.

Actual on the audit guest: no Broadcom PCI device exists (virtio-net only), so bge_attach/bge_nvram_getbyte are unreachable β€” the trigger builds clean and prints its hardware-precondition notice (run.log). Verification is therefore honestly inconclusive (source-visible asymmetry; see VERDICT.md).

Files

  • trigger.c β€” annotated hardware-precondition trigger
  • run.log β€” guest proof: pci/if_bge compiled into the kernel, NO_BROADCOM_DEVICE, sysctl hw.bge unknown, /usr/src md5 == audited tree, clean build, trigger output
  • fix.diff β€” git-apply-able release/restore on both error paths (mirrors the paired success-path cleanup at if_bge.c:584-589)
  • verdict.json / manifest.json β€” machine verdict
VERDICT.md
↓ download raw

DF-3664 VERDICT β€” bge_nvram_getbyte leaks the NVRAM arbiter on error paths

Status: INCONCLUSIVE at runtime (no bge hardware β€” and specifically no BCM5906 β€” on the audit guest); source-level CERTAIN (visible asymmetry).

Root cause

bge_nvram_getbyte() β€” sys/dev/netif/bge/if_bge.c:544-592 β€” performs the NVRAM software-arbitration handshake:

551:  CSR_WRITE_4(sc, BGE_NVRAM_SWARB, BGE_NVRAMSWARB_SET1);       /* REQUEST */
552:  for (i = 0; i < 8000; i++) {
553:      if (CSR_READ_4(sc, BGE_NVRAM_SWARB) & BGE_NVRAMSWARB_GNT1)
554:          break;
555:      DELAY(20);
556:  }
557:  if (i == 8000)
558:      return (1);                  /* BUG #1: request still latched β€”
                                         no BGE_NVRAMSWARB_CLR1 ever written */
561:  access = CSR_READ_4(sc, BGE_NVRAM_ACCESS);
562:  CSR_WRITE_4(sc, BGE_NVRAM_ACCESS, access | BGE_NVRAMACC_ENABLE);
565:  CSR_WRITE_4(sc, BGE_NVRAM_CMD, BGE_NVRAM_READCMD);
566:  for (i = 0; i < BGE_TIMEOUT * 10; i++) { ... }
574:  if (i == BGE_TIMEOUT * 10) {
575:      if_printf(&sc->arpcom.ac_if, "nvram read timed out\n");
576:      return (1);                  /* BUG #2: SWARB lock STILL HELD and
                                         BGE_NVRAMACC_ENABLE left set */
      }
...
585:  CSR_WRITE_4(sc, BGE_NVRAM_ACCESS, access);        /* paired cleanup */
588:  CSR_WRITE_4(sc, BGE_NVRAM_SWARB, BGE_NVRAMSWARB_CLR1);  /* paired */

The success path is paired (584-589) β€” this is the e1000/bnx-audit discipline the orchestrator asked to verify, and it holds for the normal path. The two timeout paths return without either releasing the arbitration request or restoring the access register.

Reachability

bge_read_nvram() only proceeds for sc->bge_asicrev == BGE_ASICREV_BCM5906 (if_bge.c:603-604); it is called at attach time by bge_get_eaddr_nvram() (if_bge.c:4977-4985) as the second station-address source. One NVM read-done or grant timeout during attach therefore leaves the arbiter held for the driver's whole lifetime.

Impact

Device-side functional denial of service only: after a timeout (i.e. on already-malfunctioning hardware) other NVRAM masters β€” ASF/management firmware, other agents sharing BGE_NVRAM_SWARB β€” can no longer arbitrate for the flash, and the NVRAM access window stays enabled. No host memory-safety impact, no unprivileged trigger. Contrast the bnx sibling DF-3640 (bnx_reset never released, filed Low): bge's defect is strictly weaker (error paths only), hence Info here.

Verification attempt (honest)

Audit guest: pci/if_bge compiled into the kernel (kldstat -v id 59) but pciconf -l shows NO_BROADCOM_DEVICE (virtio-net only) and sysctl hw.bge is an unknown oid β€” attach never runs, so neither the bug nor the fix is exercisable. Guest /usr/src if_bge.c md5 f6c2d771384a52775cd4bb399d5dcab1 == audited tree (run.log).

Fix validation

fix.diff adds the missing cleanup on both error paths, mirroring the paired success-path idiom (restore BGE_NVRAM_ACCESS, then BGE_NVRAMSWARB_CLR1 + flush read; for the grant-timeout path only the request drop, since access is not yet saved). git apply --check passes against the pristine tree. Runtime baseline-vs-patched comparison impossible for the hardware reason (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

fix.diff adds the missing cleanup on both error paths using the exact register sequence of the paired success path (restore ACCESS then CLR1 + flush read; on the grant-timeout path only the request drop since 'access' is not yet saved); git apply --check passes against the pristine tree. Cannot be runtime-validated: the guest has no BCM5906 device so neither baseline nor patched behavior is exercisable.

fix.diff (git apply --check OK); baseline absence proof in run.log (NO_BROADCOM_DEVICE, sysctl hw.bge unknown oid)
↓ fix.diffper-fix-DF-3664

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (guest proof: driver in kernel, zero Broadcom devices, no attach, src md5 match, clean -Wall build), VERDICT.md (line-accurate acquire/release asymmetry trace with the paired success path for contrast), trigger.c (annotated hardware-precondition skeleton), fix.diff (git apply --check OK; release/restore added to both error paths)

PoC changes

No seed existed (new pass-2 finding); trigger written as an annotated hardware-precondition skeleton per the DF-3639/DF-3640 precedent, built and executed on the guest to prove the code path is unreachable there.

Verified recommended fix

In bge_nvram_getbyte, drop the arbitration request (BGE_NVRAMSWARB_CLR1) on the grant-timeout path and both restore BGE_NVRAM_ACCESS and release the lock on the read-timeout path, mirroring the success-path cleanup at if_bge.c:584-589 (see fix.diff).

Verdict

Source-level certain (visible asymmetry): bge_nvram_getbyte (sys/dev/netif/bge/if_bge.c:544-592) pairs the BGE_NVRAM_SWARB acquire (SET1, line 551) with release (CLR1, line 588) and the BGE_NVRAM_ACCESS save/restore (561-562 vs 585) on the SUCCESS path β€” the discipline the bnx sibling DF-3640 lacked β€” but its two error paths return without cleanup: the grant-timeout path at 557-558 leaves the SET1 request latched, and the read-done timeout path at 574-577 additionally leaves BGE_NVRAMACC_ENABLE set while holding the arbiter. Reachable only at attach time on BCM5906 (bge_read_nvram gates on asicrev==BCM5906, if_bge.c:603-604; called from bge_get_eaddr_nvram, if_bge.c:4977-4985). Impact: after one NVRAM timeout (already-malfunctioning hardware) the flash arbiter stays held for the driver's lifetime, starving ASF/management firmware of NVRAM β€” device-side functional DoS only, no host memory-safety impact, no unprivileged trigger. Runtime verification honestly inconclusive: the audit guest has virtio-net only; bge is compiled into the kernel (kldstat -v: pci/if_bge id 59) but no Broadcom PCI device exists, so bge_attach/bge_nvram_getbyte are unreachable (run.log: NO_BROADCOM_DEVICE, sysctl hw.bge unknown oid). Guest /usr/src if_bge.c md5 f6c2d771384a52775cd4bb399d5dcab1 is byte-identical to the audited tree.