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

aux_engine: read-continuation chunks copied to buffer BASE instead of buffer+offset β€” corruption of partial-ACK DPCD/EDID read data (twin in dce_aux.c)

Summary

process_read_request sets reply.data=buffer while the multi-iteration loop advances ctx->offset - every continuation chunk of a partially-ACKed native DPCD/EDID read lands at offset 0, overwriting the first chunk; the transaction succeeds with corrupted data (link training/caps/EDID on DCE80-120). In-bounds (data-integrity bug, not memory unsafety). Identical line at dce/dce_aux.c:502. buffer+offset fix in both engines.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4778 Β· 5 files
FileTypeDescriptionSize
README.md readme finding + reproduce + fix 2.6 KB ↓ raw
VERDICT.md verdict line-level trace, bound-safety, reachability, severity 4.0 KB ↓ raw
fix.diff suggested-fix reply.data offset fix in aux_engine.c + dce_aux.c 866 B view raw
manifest.json manifest this file 1.2 KB view raw
verdict.json verdict machine verdict 2.7 KB view raw
README.md readme finding + reproduce + fix
↓ download raw

DF-4778 β€” aux_engine.c read-continuation copies reply chunks to buffer BASE, not buffer + offset

Finding

sys/dev/drm/amd/display/dc/i2caux/aux_engine.c (base class for the DCE80–DCE120 AUX engines) β€” process_read_request() sets

ctx->reply.length = ctx->returned_byte;
ctx->reply.data   = ctx->buffer;          /* line 204-205: missing "+ ctx->offset" */

while the multi-iteration read loop in read_command() advances ctx->offset += ctx->returned_byte on every partial ACK (process_read_reply(), aux_engine.c:132-135) and re-points the request at buffer + offset (aux_engine.c:284-287). The concrete engine (i2caux/dce110/aux_engine_dce110.c:281-331 read_channel_reply) copies reply->length bytes from the AUX SW data registers into reply->data β€” i.e. into the BASE of the caller buffer on every iteration. Consequences on a continued (partial-ACK) native DPCD / I2C-over-AUX read:

  • iteration 2+ overwrites bytes [0, returned_byte) instead of appending at [offset, offset+returned_byte) β€” the first chunk's data is destroyed and the caller (EDID / DPCD / link-training readers) gets chunk #2 at offset 0, chunk #3 at offset 0 again, etc.;
  • request->data (used by the hardware submit path) and reply->data (used by the software reply path) disagree β€” the transaction returns "success" with corrupted contents.

The copy stays in bounds (returned_byte ≀ current_read_length is enforced at aux_engine.c:196, and offset + current_read_length is invariant), so this is a data-integrity bug in the display-link configuration path, not a memory- safety bug. The identical line exists in the newer sibling engine sys/dev/drm/amd/display/dc/dce/dce_aux.c:502 (shared DAL lineage; Linux removed the whole i2caux/ tree in 2018).

Reproduce

Not runnable on the audit guest: the path requires an AMD GPU (DCE80–DCE120 DisplayPort AUX engine) β€” the QEMU guest has no AMD graphics device and the amdgpu/DC code is not in the loaded kernel. Reproduction on hardware needs a DP sink that ACKs a native DPCD read partially (fewer bytes than requested) and a DPCD read of length > 16 (or any read that takes 2+ iterations), then comparing the returned bytes with the expected chunk order β€” chunk N+1 will appear at offset 0.

Fix

fix.diff (this directory): ctx->reply.data = ctx->buffer + ctx->offset; in both i2caux/aux_engine.c and dce/dce_aux.c. Compile-validated only (no HW).

Files

VERDICT.md full analysis + why it cannot run on this guest fix.diff git-apply-able unified diff (both engines) manifest.json machine-readable catalog

VERDICT.md verdict line-level trace, bound-safety, reachability, severity
↓ download raw

DF-4778 β€” VERDICT

UNTESTED at runtime (no AMD GPU on the audit guest); code defect CONFIRMED by line-level trace. Not a memory-safety violation β€” in-bounds data corruption of DisplayPort AUX read data.

Root cause (line-level)

sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:

  • read_command() (aux_engine.c:245-307) loops a native/I2C-over-AUX read across partial ACKs. Each iteration:
  • memset(ctx.buffer + ctx.offset, 0, ctx.current_read_length) (line 284)
  • ctx.request.data = ctx.buffer + ctx->offset; (line 286)
  • process_read_reply() (aux_engine.c:119-181) on AUX_ACK with returned_byte < current_read_length advances ctx->offset += ctx->returned_byte (line 135) and continues.
  • process_read_request() (aux_engine.c:183-243) then sets ctx->reply.length = ctx->returned_byte; ctx->reply.data = ctx->buffer; (lines 204-205) β€” the base, not ctx->buffer + ctx->offset.
  • The concrete engine copies the reply payload into reply->data: i2caux/dce110/aux_engine_dce110.c:300-325 read_channel_reply reads the AUX_SW_DATA registers into buffer[i] for i < bytes_replied, bounded by size == reply->length.

So on iteration 2+ of a continued read, the freshly received chunk lands at buffer[0..returned) while the protocol state expects it at buffer[offset..offset+returned). The bound check (returned_byte > current_read_length at line 196, plus the invariant offset + current_read_length == original length) keeps every copy inside the caller's buffer β€” no OOB write β€” but the assembled DPCD/EDID data is wrong (chunk #2 overwrites chunk #1's tail region start).

Twin: sys/dev/drm/amd/display/dc/dce/dce_aux.c:501-502 has the identical ctx->reply.data = ctx->buffer; in its own process_read_request (dce_aux.c:486-506), reached from read_command (dce_aux.c:547-600) β€” the whole DCN+ path shares the bug.

Reachability

  • Trigger condition: a DP sink that ACKs a read with fewer bytes than requested (returned_byte < current_read_length). This is a legal and real-world sink behavior β€” it is precisely the case the retry logic at aux_engine.c:132-144 exists to handle.
  • Impact surface: native DPCD reads (link training, capability probes) and I2C-over-AUX reads (EDID) on DCE80–DCE120 ASICs via dal_aux_engine_submit_request() (aux_engine.c:560-591) from dal_i2caux_submit_aux_command() (i2caux/i2caux.c:196-267) / dc_link_ddc.c:598. Corrupted link-training/EDID data β†’ unstable or misconfigured display links, wrong modes. No kernel memory unsafety.

Why not run on the guest

The audit guest (QEMU/KVM, no AMD graphics device) cannot instantiate any DCE AUX engine: amdgpu/DC is not compiled into the booted kernel and no AMD hardware exists behind the QEMU PCI bus. The finding is therefore filed as runtime-UNTESTED with code-level certainty (the defect and its bound- safety were fully traced; see kernel_refs).

Severity reasoning

Low: in-bounds data corruption inside a display configuration path; requires specific (legal) sink behavior; no confidentiality/integrity impact on kernel memory; worst realistic outcome is display malfunction persisted until the next successful AUX read.

Fix

fix.diff: point reply.data at buffer + offset in both engines (aux_engine.c:205, dce_aux.c:502). Compile-validated only (no HW to run).

kernel_refs

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 applies to both engines; cannot boot-test on a guest without AMD hardware

fix.diff (2 hunks, aux_engine.c:205 + dce_aux.c:502)
↓ fix.diffper-fix-DF-4778

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (line-level trace + bound-safety argument + reachability); fix.diff (offset fix in both engines)

PoC changes

n/a (source-level finding; no runnable PoC on this guest)

Verified recommended fix

ctx->reply.data = ctx->buffer + ctx->offset; in i2caux/aux_engine.c and dce/dce_aux.c (see fix.diff)

Verdict

Runtime-untested: requires an AMD DCE80-DCE120 DisplayPort AUX engine and a partially-ACKing DP sink, neither present on the audit QEMU guest. Code defect confirmed by line-level trace: process_read_request() points ctx->reply.data at ctx->buffer instead of ctx->buffer + ctx->offset (aux_engine.c:204-205), so every continuation chunk of a partial-ACK native/I2C-AUX read overwrites the start of the caller buffer instead of appending at offset. Copy remains in bounds (aux_engine.c:196 bound + offset invariant), so impact is corrupted DPCD/EDID read data (display link misconfiguration), not kernel memory unsafety. Identical line in the twin engine dce/dce_aux.c:502.