⬢ DragonFlyBSD Kernel Audit
DF-5035 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5035",
  "status": "untested",
  "reproduced": false,
  "impact": "none",
  "confidence": "speculative",
  "verdict": "acquire_i2c_hw_engine() guards the chip-wide single DC_I2C HW transaction buffer with a lock-free check-then-set of i2c_hw_buffer_in_use (i2caux_dce110.c:105-109), and release_engine() clears it lock-free (:120-122), while the whole i2caux subsystem is unsynchronized (the port itself notes the dropped DAL2 Mutex at i2caux.h:91). Two concurrent dal_i2caux_submit_i2c_command() calls on different connectors can both pass the flag check, both succeed in the per-line gpio acquire (distinct pins, no cross-line serialization), and both drive the single shared DC_I2C engine with interleaved state -> corrupted EDID/DDC transactions (display-detection functional DoS on real DCE11 hardware). No kernel-memory write is reachable through this race. Untested: requires an AMD DCE11 ASIC with two connectors plus a concurrent HPD/ioctl EDID workload; the QEMU guest has only a virtio GPU and no drm KLD. Acquire-side dce110 twin of release-side DF-4878 (i2caux.c).",
  "exploit_chain": "on-target only: thread A (HPD work, connector 1) + thread B (DRM ioctl, connector 2) both submit I2C_COMMAND_ENGINE_HW -> both acquire the shared HW engine -> interleaved DC_I2C register/transaction-buffer programming -> garbage EDID bytes / AUX timeouts; no escalation path.",
  "evidence": "README.md and VERDICT.md (full interleaving trace); run ./analysis.sh to print i2caux_dce110.c:79-125 (the racy check-then-set and release clear), i2caux.h:55-98 (no lock in struct i2caux, DAL2 mutex comment at :91), i2caux.c:109-194 (lock-free submit), and the shared-state references in i2c_hw_engine_dce110.c.",
  "kernel_refs": [
    "sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c:105",
    "sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c:120",
    "sys/dev/drm/amd/display/dc/i2caux/i2caux.h:91",
    "sys/dev/drm/amd/display/dc/i2caux/i2caux.c:135"
  ],
  "poc_changes": "No PoC written \u2014 hardware-gated race (needs DCE11 ASIC, two connectors, concurrent HPD+ioctl); evidence is a mechanical source-print script.",
  "attempts": 0,
  "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": 0,
  "guest_dirty": 0,
  "build_cmd": "",
  "run_cmd": "",
  "code_hash": "",
  "notes": "Same root cause family as DF-4878 (mutex dropped from DAL2) but distinct field/file: that finding is the release-side engine->ddc NULL-wipe race in i2caux.c; this is the dce110-private acquire-side buffer flag. Ceiling on real HW is corrupted DDC/EDID transactions (detection DoS); kernel memory is not written through this path. Minimal atomic-test-and-set would close only the double-acquire; a single i2caux mutex (see fix.diff) closes both this and DF-4878.",
  "recommended_fix": "Restore the DAL2 mutex: serialize acquire_i2c_hw_engines check-acquire-set and release_engines flag clear (and engine->ddc handling per DF-4878) under one i2caux lock \u2014 see fix.diff.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": false,
  "fix_patched_reproduced": false,
  "fix_verdict": "n/a \u2014 race not reproduced (no AMD ASIC in the test guest); fix shape reviewed only.",
  "fix_evidence": "fix.diff (ILLUSTRATIVE, file-only: serializes the check-then-set and the release clear with a DragonFly lockmgr; requires adding struct lock hw_buffer_lock to struct i2caux_dce110 in i2caux_dce110.h plus lockinit/uninit in construct/destruct \u2014 diff validates with git apply --check against this file only)."
}