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)
PoC verification
Evidence pack
findings/poc/DF-4778 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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) andreply->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
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) onAUX_ACKwithreturned_byte < current_read_lengthadvancesctx->offset += ctx->returned_byte(line 135) and continues.process_read_request()(aux_engine.c:183-243) then setsctx->reply.length = ctx->returned_byte; ctx->reply.data = ctx->buffer;(lines 204-205) β the base, notctx->buffer + ctx->offset.- The concrete engine copies the reply payload into
reply->data:i2caux/dce110/aux_engine_dce110.c:300-325 read_channel_replyreads the AUX_SW_DATA registers intobuffer[i]fori < bytes_replied, bounded bysize == 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) fromdal_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
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:132-135 (offset advance)
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:196-208 (reply.data = buffer)
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:283-296 (request at offset)
- sys/dev/drm/amd/display/dc/i2caux/dce110/aux_engine_dce110.c:281-331 (copy)
- sys/dev/drm/amd/display/dc/dce/dce_aux.c:493-506 (twin)
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_testablefix.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)
Confirmed kernel references
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:132
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:196
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:204
- sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:284
- sys/dev/drm/amd/display/dc/i2caux/dce110/aux_engine_dce110.c:281
- sys/dev/drm/amd/display/dc/dce/dce_aux.c:502
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.
No comments yet.