⬢ DragonFlyBSD Kernel Audit
DF-4683 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4683",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 5,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df2317 && sh build.sh",
  "run_cmd": "./ar2317_harness 4 && ./ar2317_harness 5",
  "code_hash": "2a6f1714d2a77176c8c164e7a8ea4dd9153f22a3bdecb466c9af8ded31a0d901",
  "verdict": "REPRODUCED on the DragonFly guest (verbatim-extraction harness, modes 4 and 5) -- a heap OOB WRITE one class stronger than the ar2317 twins and NOT covered by them: neither ar2317FillVpdTables loop nor the interpolate loop clamps the pd-gain power span to the vpdTable row width (MAX_PWR_RANGE_IN_HALF_DB=64, ah_eeprom_v3.h:291) or validates Pmax>=Pmin. (Mode 4, wrap variant) a DESCENDING EEPROM pwr_t4 curve yields Pmax_t2=84 < Pmin_t2=90 (ar2317.c:372-382 performs no monotonicity check), so while (ii <= (uint16_t)(Pmax - Pmin)) at ar2317.c:289 wraps to ii<=65530 and the loop writes retVpdList[pdGainIdx][ii] for ii=0..65530 -- up to 131060 bytes marching from vpdTable_L[0][0] through the whole 2008-byte ar2317State and off its end; the harness (object placed with its end at a page boundary and a guard page after) faulted on the STORE at exactly the struct end, proving the write escaped the heap object. (Mode 5, clamp variant) a perfectly VALID ascending curve spanning 102 half-dB (>64) needs no integer wrap: FillVpdTable writes vpdTable_L/R row 3 indices 64..102 (intra-object spill) and the interpolate loop at ar2317.c:393-398 writes VpdTable_I[3][0..101] -- vpdTable_I is the LAST member, so indices >=64 land past the object end; faulted at exactly &vpdTable_I[3][64] == struct end. Written values are EEPROM-Vpd-derived (0..255, or 0xFFxx from negative extrapolation), so in-kernel this is a semi-controlled kernel-heap overwrite of up to ~128 KB followed by adjacent-object corruption or an immediate panic. The family pass-2s (DF-4619..4662) filed only the kk/gain-count/flat-curve siblings; this span defect is present in all five ar5212 RF files (ar2317.c:289 == ar2413.c:309 == ar2316.c:312 == ar5413.c:353 == ar2425.c:311) but is filed here first. Fix validated: fixD (reject inverted/oversized spans via continue) makes mode 5 complete with fixD alone; mode 4 completes with fixD+fixB (skipping the inverted gain leaves zero gains -- DF-4681s early return covers that residual).",
  "exploit_chain": "crafted v3 EEPROM on AR2317 card: (a) descending pwr_t4 curve -> (uint16_t)(Pmax-Pmin) wrap -> ~131 KB sequential uint16 overwrite from vpdTable_L through and past the ath_hal_mallocd ar2317State into adjacent kernel heap objects -> corruption/panic; or (b) valid curve spanning >63 half-dB -> up to 128 bytes past the object end via VpdTable_I[3][64..]. Values are Vpd-derived (0..255 / 0xFFxx). No uid0 route demonstrated (gated on attacker-supplied AR2317 hardware); in-kernel ceiling is adjacent-heap corruption with semi-controlled data.",
  "evidence": "run.log mode 4: REPRODUCED: SIGSEGV si_addr=0x800476000 (== guard == struct end, si_addr is 2008 bytes past struct start), FillVpdTable loop bound (uint16_t)(84-90) = 65530 entries (131060 bytes) into a 64-wide row, MATCH: unbounded OOB WRITE ran off the end of the ar2317State heap object; run.log mode 5: same guard fault from the valid 102-half-dB curve; fix_run.log: fixD-only mode 5 completes (numPdGainsUsed=3), fixB+fixD mode 4 completes (ret=0, sentinel minPower).",
  "kernel_refs": [
    "sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:289-306",
    "sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:273-309",
    "sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:372-382",
    "sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:393-398",
    "sys/dev/netif/ath/ath_hal/ar5212/ar2317.c:59-60",
    "sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:291",
    "sys/dev/netif/ath/ath_hal/ah_eeprom_v3.h:294-314"
  ],
  "poc_changes": "New modes 4/5 added to the DF-4680 harness: object placed at a page end with a PROT_NONE guard page so OOB WRITEs fault at exactly struct end; modes 1-3 unchanged from the twin harness.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Validated on the guest: fix.diff (skip any pdGain whose span is inverted (Pmax<Pmin) or >= MAX_PWR_RANGE_IN_HALF_DB via continue before the FillVpdTable/interpolate calls) applied to a copy of the pristine guest ar2317.c. Mode 5 completes with fixD alone (no SIGSEGV, numPdGainsUsed=3 -- the oversized gain is skipped). Mode 4: fixD alone leaves numPdGainsUsed==0 (all other gains unused), reaching the DF-4681 wild-read path, so fixB.diff (DF-4681s early return, also in this pack) is applied with it: mode 4 then completes with ret=0 and sentinel minPower. fix.diff is git-apply-able against the pristine tree and was never applied to sys/.",
  "fix_evidence": "fix_run.log (===== fixD and ===== fixD + fixB sections: PATCHES_APPLIED, FIXED_BUILD_OK, both modes no SIGSEGV: function completed); run.log modes 4/5 for the baseline guard-page faults.",
  "attempts": 1,
  "notes": "Rated Medium (vs Low for the twins) because this is an OOB WRITE beyond the heap object with up to ~128 KB reach and semi-controlled data, not an intra-object spill or a wild read. Same defect exists verbatim in ar2413.c:309, ar2316.c:312, ar5413.c:353, ar2425.c:311 (the family twins packs did not cover it); recommend fixing all five together. Known DF-1806/DF-4681/DF-4682 are different triggers in the same function and were NOT re-reported.",
  "recommended_fix": "Reject pd-gain curves with Pmax_t2 < Pmin_t2 or span >= MAX_PWR_RANGE_IN_HALF_DB before calling ar2317FillVpdTable / the interpolate loop (fix.diff; mirror to ar2413/ar2316/ar5413/ar2425)."
}