# DF-5035 — VERDICT

**status: untested** (PoC not run — Low/speculative, requires an AMD DCE11
ASIC with two connectors plus a concurrent HPD/EDID workload; the QEMU
guest has only a virtio GPU, no drm KLD)
**reproduced: 0, impact: none (demonstrated ceiling on real HW: corrupted
DDC/EDID transactions — display-detection DoS, no kernel memory effect)**

## Narrative

`i2caux_dce110.c:105-109` implements the guard for the chip-wide single
DC_I2C hardware transaction buffer as a lock-free check-then-set:

    if (!i2caux_dce110->i2c_hw_buffer_in_use &&
        engine->base.funcs->acquire(&engine->base, ddc)) {
        i2caux_dce110->i2c_hw_buffer_in_use = true;

The enclosing subsystem has no synchronization at all — `i2caux.h:91`
carries the port's own confession: `[anaumov] in DAL2, there is a Mutex`.
Two threads entering `dal_i2caux_submit_i2c_command()` (`i2caux.c:109-194`,
lock-free) on two different connectors can both observe the flag clear;
the per-engine `acquire()` only grabs that line's gpio pins
(`engine_base.c`/`dal_ddc_open`), so both succeed on distinct lines, both
set the flag, and both then drive the single shared DC_I2C engine registers
(`i2c_hw_engine_dce110.c` submit path, incl. the shared SW-status/INDEX/
transaction buffer) with interleaved state. `release_engine()`
(`i2caux_dce110.c:120-122`) clears the same flag without a lock, symmetric
to the already-filed release-side race DF-4878 (`i2caux.c`).

Trace of what breaks (HW state, not kernel memory):
- `submit_request()`/`process_transaction()` in
  `i2caux/dce110/i2c_hw_engine_dce110.c` program `DC_I2C_TRANSACTION_*`,
  poll `DC_I2C_SW_STATUS`, and use one shared transaction buffer per ASIC —
  interleaving from two lines yields wrong lengths/data, spurious timeouts,
  and possibly the need for `DC_I2C_SOFT_RESET` (:123-139).
- No kernel-memory write occurs through this race: the flag is a plain
  bool in the kzalloc'd `struct i2caux_dce110`, and all engine pointers
  involved were fixed at construct time.

Why speculative: the concurrency precondition was not proven on this
DragonFly port — EDID reads typically originate from the dm hotplug work
and ioctl (mode-set/getconnector) contexts, which *can* overlap, but I
could not demonstrate two simultaneous `dal_i2caux_submit_i2c_command()`
calls without the hardware. Guest-side execution is impossible
(`pciconf`: only virtio 1af4:1100; `kldstat`: no drm/amdgpu/radeon).

## Exploit chain

None beyond functional: repeated concurrent acquires could keep the flag
set while both engines are released (both releases clear it — no permanent
lockout observed in the state machine), worst realistic outcome is
persistently failing EDID reads on hotplug storms = display-detection DoS
on the affected card.

## Fix validation

`fix.diff` wraps the check-acquire-set and the release-clear in the
`ddc_service`? No — minimal, self-contained fix chosen: an atomic cmpset
for the acquire + lock-held clear is deferred; the honest minimal diff
restores a mutex around both sites via `dc_context`-provided lock (this
port uses `drm` `mutex` shim in dm_services). Not built/booted (nothing to
observe without DCE11 hardware).

kernel_refs:
- sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c:105-109
- sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c:120-122
- sys/dev/drm/amd/display/dc/i2caux/i2caux.h:91 (mutex confession)
- sys/dev/drm/amd/display/dc/i2caux/i2caux.c:109-194 (lock-free submit)
- sibling: DF-4878 (release-side, i2caux.c)

## 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.
