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

dm_dp_aux_transfer discards dc_link_aux_transfer() return on both AUX write paths: failed DPCD/I2C writes reported as full success to the DRM DP core (MST sideband desync on failing/hostile DP hardware)

Summary

Both WRITE cases ignore the return and unconditionally report msg->size - NACKed/timed-out writes look successful to drm_dp_dpcd_write and the MST sideband advances state the branch never received: persistent display deadlocks/wedged topology on hostile-dongle class. Assign-the-return fix (mirrors upstream unification).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4911 Β· 10 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
aux_chardev_probe.c β€” 973 B view raw
build.sh β€” 108 B view raw
run.sh β€” 148 B view raw
run.log β€” 166 B view raw
env.txt β€” 347 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 2.7 KB view raw
manifest.json β€” 954 B view raw

DF-4911 β€” dm_dp_aux_transfer discards dc_link_aux_transfer() return value on both AUX write paths, reporting failed DPCD/I2C writes as full success

File: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c:105-117,133-148 Severity: Low | CWE-252 | confidence: certain (defect) / likely (impact)

Build / run

The bug is in the kernel's AUX transfer callback; it is exercised whenever the amdgpu DM driver performs DPCD/AUX writes (link training, MST sideband messaging) against real DisplayPort hardware. There is no direct unprivileged trigger: the raw AUX character device (/dev/drm_dp_auxN) is compiled out on DragonFly (CONFIG_DRM_DP_AUX_CHARDEV is not set in sys/dev/drm/kconfig.h), so writes are kernel-initiated.

./build.sh   # cc -o aux_chardev_probe aux_chardev_probe.c
./run.sh     # on AMD hardware with CONFIG_DRM_DP_AUX_CHARDEV: write to a DPCD
             # address while the sink NACKs; drm dp core logs success anyway.

Expected result (on AMD DP hardware with failing/NACKing sink)

dc_link_aux_transfer returns -1 on AUX_CHANNEL_OPERATION_FAILED_TIMEOUT / INVALID_REPLY (sys/dev/drm/amd/display/dc/core/dc_link_ddc.c:669-683), but dm_dp_aux_transfer executes:

    case DP_AUX_NATIVE_WRITE:
        ...
        dc_link_aux_transfer(...);          /* return value dropped */
        result = msg->size;                 /* unconditional success */

so drm_dp_dpcd_write() callers (sys/dev/drm/drm_dp_helper.c:334+) see msg->size (>0 == success) for a write that never reached the sink. The MST sideband engine then advances message state / believes payload allocations were programmed while the branch device never received them -> topology desync, wedged links, black screens. A hostile DP dongle can NACK every write deliberately, turning this into a persistent display-DoS amplifier and confusing the kernel's model of programmed payload tables.

Actual result on this audit guest

No AMD display device, no /dev/dri, aux chardev compiled out β€” runtime unreachable; status honestly untested. Defect itself is certain from source.

VERDICT.md
↓ download raw

VERDICT β€” DF-4911

status: untested (runtime requires AMD DM hardware performing real AUX writes; the audit guest has no AMD display device and the raw AUX chardev is compiled out β€” CONFIG_DRM_DP_AUX_CHARDEV absent from sys/dev/drm/kconfig.h, verified sys/conf/files:2294 builds drm_dp_aux_dev.c but registration is gated on that config). reproduced: 0.

The defect (certain, from source)

dm_dp_aux_transfer (sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c):

  • DP_AUX_NATIVE_WRITE case, lines 105-117: the return value of dc_link_aux_transfer() is discarded and result = msg->size; is assigned unconditionally.
  • DP_AUX_I2C_WRITE case, lines 133-148: identical discard.

dc_link_aux_transfer (sys/dev/drm/amd/display/dc/core/dc_link_ddc.c:635-686) returns -1 for AUX_CHANNEL_OPERATION_FAILED_REASON_UNKNOWN / INVALID_REPLY / TIMEOUT (lines 678-682) and 0 for HPD-disconnect (line 675-677). None of these reach the caller on the write paths: the DRM DP core (drm_dp_dpcd_write, sys/dev/drm/drm_dp_helper.c:334) interprets the returned msg->size (> 0) as full success.

Consequence: a DPCD/I2C write that NACKed, timed out, or raced an HPD disconnect is silently reported as delivered. Consumers that trust it:

  • MST sideband DOWN_REQ transmission (drm_dp_mst_topology.c tx path builds messages with drm_dp_dpcd_write to DP_SIDEBAND_MSG_DOWN_REQ_BASE) β€” the core's sequence numbers advance although the branch never saw the request; the topology state machine desyncs from the hub.
  • Payload allocation writes (PAYLOAD_ALLOC_SET) believed programmed.
  • Link-training DPCD writes believed acknowledged.

Threat: a hostile or failing DP/MST device (evil-dongle class) that NACKs writes while the driver reads plausible DPCD data causes persistent display deadlocks and payload-table confusion β€” functional DoS, no memory corruption identified (sizes are separately capped at 16 by the WARN_ON(msg->size > 16) guard at line 89, which is correct; msg->reply is filled by DC on all paths). Impact ceiling: local display DoS on AMD hardware; hence Low.

Guest evidence

run.log: probe builds; guest reports NO_AMD_PCI_DEVICE, NO_DEV_DRI, and no /dev/drm_dp_aux* devices. Path unreachable at runtime on this guest.

Fix

Assign the return value on both write paths exactly like the read paths (fix.diff, mirrors the eventual upstream unification of all four cases).

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 cannot be validated on this guest (baseline path needs AMD DP hardware); fix.diff is a trivial, behavior-preserving-on-success change mirroring the read cases.

fix.diff
↓ fix.diffper-fix-DF-4911

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (source trace + consumer analysis), run.log (guest: NO_AMD_PCI_DEVICE / NO_DEV_DRI / no aux chardev), fix.diff (assign result on write paths).

PoC changes

No seeded PoC; wrote aux_chardev_probe.c to document the (disabled) direct userspace handle.

Verified recommended fix

Propagate dc_link_aux_transfer()'s return value on the NATIVE_WRITE and I2C_WRITE cases instead of unconditionally returning msg->size (see fix.diff).

Verdict

Certain code defect, untested at runtime (no AMD display hardware on the audit guest; raw AUX chardev compiled out β€” CONFIG_DRM_DP_AUX_CHARDEV not defined in sys/dev/drm/kconfig.h). dm_dp_aux_transfer discards the dc_link_aux_transfer() return on both write cases (amdgpu_dm_mst_types.c:105-117 NATIVE_WRITE, 133-148 I2C_WRITE) and returns msg->size unconditionally, so NACKed/timed-out/HPD-raced DPCD and I2C-over-AUX writes are reported to the DRM DP core as full success (dc_link_aux_transfer returns -1/0 on those failures, dc_link_ddc.c:669-683). MST sideband DOWN_REQ sequencing and payload-alloc programming then desync from reality on failing or hostile DP hardware β€” persistent display DoS; no memory-safety impact found (16-byte cap at :89 is correct).