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

dce_aux: stack buffer overflow in AUX write-reply path (bound check compares HW field against itself)

Field Value
ID DF-1694
File sys/dev/drm/amd/display/dc/dce/dce_aux.c
Lines 285, 288, 291, 292, 294, 690, 691
Severity Low
CVSS 3.1 CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE CWE-787 Out-of-bounds Write
Confidence likely
Status new
CVE match variant (DP sink reply length OOB β€” twin of i2caux/aux_engine.c which IS reachable; affects upstream Linux amdgpu)
Created 2026-07-18

Summary

In the write-command path, ctx.reply_data is a fixed 16-byte stack buffer (DEFAULT_AUX_MAX_DATA_SIZE, aux_engine.h:135), but read_channel_reply's overflow guard if (bytes_replied > size) return -1 (dce_aux.c:285) compares the HW-reported AUX_SW_REPLY_BYTE_COUNT against size, where size is ctx.reply.length which itself was just set to ctx.returned_byte β€” the SAME 5-bit HW field (mask 0x1f000000, max 31).

A malicious DisplayPort sink that returns >17 bytes in reply to a write can drive bytes_replied up to 30, pass the meaningless self-comparison, and overflow ctx.reply_data on the kernel stack by up to 14 bytes.

Root cause

process_write_request (dce_aux.c:690-691) sets:

ctx->reply.length = ctx->returned_byte;
ctx->reply.data   = ctx->reply_data;

where ctx->returned_byte (uint8_t, set by get_channel_status at dce_aux.c:398-407 from AUX_SW_STATUS.AUX_SW_REPLY_BYTE_COUNT, masked to 5 bits β†’ 0..31, then decremented by 1 β†’ 0..30) is in NO way bounded against sizeof(ctx->reply_data) == 16.

read_channel_reply (dce_aux.c:250-300) then re-reads the SAME HW field into a local bytes_replied (line 258), decrements it (line 282), and gates the write loop with if (bytes_replied > size) return -1 (line 285). Because both size and bytes_replied are derived from AUX_SW_REPLY_BYTE_COUNT, an attacker who makes the sink emit a consistent oversized reply (count = N, 18 ≀ N ≀ 31) gets size = N-1 and bytes_replied = N-1, so the guard reduces to N-1 > N-1 = false, and the loop at lines 288-294 writes N-1 bytes (17..30) into the 16-byte ctx.reply_data.

The correct bound should be DEFAULT_AUX_MAX_DATA_SIZE (16), NOT a re-read of the attacker-influenced HW register.

Threat model

Attacker position: a malicious DisplayPort sink (rogue monitor, hostile USB-C dock/DP-HDMI dongle, or compromised MST hub) plugged into the target machine. The sink responds to an AUX write transaction (e.g. DPCD write during link training, which the kernel issues automatically on hotplug) with an AUX reply carrying 18 or more bytes instead of the spec-mandated single status byte.

The DP AUX standard caps native replies at 1 byte for writes / 17 bytes for reads, but the AMD HW register field AUX_SW_REPLY_BYTE_COUNT is 5 bits wide (dce_11_0_sh_mask.h:8677, mask 0x1f000000) and will faithfully count any extra bytes the sink emits.

Impact: kernel stack overflow of up to 14 bytes past ctx.reply_data in write_command()'s stack frame, overwriting transaction_complete and operation_succeeded and continuing into the stack canary / saved frame. With -fstack-protector this becomes a reliable kernel panic (local DoS from physically-adjacent attacker); without the canary, or if the canary check is somehow bypassed, it is a kernel-RCE primitive from a plugged-in device.

IMPORTANT REACHABILITY CAVEAT for this specific file: the dce_aux.c submit_request function (registered at line 908 in aux_engine_funcs) is NOT invoked by any in-tree caller on a DC res_pool engine β€” the only consumer of these engines is dc_link_aux_transfer (dc_link_ddc.c:655), which bypasses submit_request and calls submit_channel_request/get_channel_status/read_channel_reply directly with caller-bounded size <= 16, which is safe.

HOWEVER the IDENTICAL bug pattern is present and IS reachable in the parallel i2caux implementation (sys/dev/drm/amd/display/dc/i2caux/aux_engine.c:309-332 + i2caux/dce110/aux_engine_dce110.c, whose submit_request IS called via dal_i2caux_submit_aux_command from dc_link_ddc.c:598), so the bug class is live in the same kernel image; fixing it here is defense-in-depth against future refactoring that wires DC engines through submit_request.

PoC

findings/poc/DF-1694/:

Triggering the dce_aux.c instance directly requires submitting a transaction through this engine's submit_request (currently uncalled). To prove the bug in isolation, build a small kernel module that:

  1. obtains an aux_engine from dc->res_pool->engines[i] (dc_link_ddc.c:655 pattern)
  2. calls engine->funcs->acquire(engine, ddc_pin)
  3. constructs an i2caux_transaction_request with operation=I2CAUX_TRANSACTION_WRITE, payload.address_space=I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD, payload.length=16, payload.data=scratch_buf
  4. attaches a malicious DP sink (e.g. a FPCA/FPGA DP source emulator, or a Linux machine with a USB-C DP-alt-mode controller reprogrammed via its AUX state machine) that responds to AUX native writes with 20+ reply bytes
  5. calls engine->funcs->submit_request(engine, &request, true)

Success criterion: kernel panic with stack-protector failure (__stack_chk_fail) or, with CONFIG_CC_STACKPROTECTOR_NONE, unexplained corruption/panic in or near write_command().

For the actually-reachable twin bug in the i2caux file, no module is needed: just plug the malicious sink into a running DF/amdgpu system; the overflow fires from the kernel's own DPCD writes during link training.

The PoC source for the malicious-sink side is an Arduino/FPGA DP AUX responder that, on receiving any AUX_NATIVE_WRITE request, returns a reply packet whose body length field is set to e.g. 20 and prepends 20 junk bytes after the 4-bit ACK status; the host's AUX_SW_REPLY_BYTE_COUNT then reads back 21, ctx.reply_data overflows by 4 bytes.

Place PoC sources under findings/poc/DF-1694/ as:

  • malicious_sink.ino (or emulate_dp_sink.py using a USB-C DP-alt-mode SINK eval board)
  • trigger_write.c (kernel module that drives submit_request on res_pool->engines[0])
  • build.sh (kldload / cc)
  • run.sh
  • expected_panic.txt containing the stack-protector signature

The bound check must compare against the actual buffer capacity, not against a re-read of the same attacker-influenced HW field.

The actual fix belongs in process_write_request, which must cap ctx->reply.length at DEFAULT_AUX_MAX_DATA_SIZE before assigning it to ctx->reply.length, because ctx->reply.data is only DEFAULT_AUX_MAX_DATA_SIZE bytes:

--- a/sys/dev/drm/amd/display/dc/dce/dce_aux.c
+++ b/sys/dev/drm/amd/display/dc/dce/dce_aux.c
@@ -687,8 +687,14 @@ static void process_write_request(
        ctx->timed_out_retry_aux = 0;
        ctx->invalid_reply_retry_aux = 0;

-       ctx->reply.length = ctx->returned_byte;
+       /* ctx->reply_data is DEFAULT_AUX_MAX_DATA_SIZE (16) bytes; the HW
+        * AUX_SW_REPLY_BYTE_COUNT field is 5 bits wide and a malicious sink
+        * can drive it up to 31. Cap the reply length we feed to
+        * read_channel_reply at the actual buffer capacity. */
+       ctx->reply.length = min_t(uint32_t, ctx->returned_byte,
+                     DEFAULT_AUX_MAX_DATA_SIZE);
        ctx->reply.data = ctx->reply_data;

Apply the analogous fix to the reachable twin in sys/dev/drm/amd/display/dc/i2caux/aux_engine.c (process_write_request, around line ~453 where ctx->reply.length = ctx->returned_byte is set), which is the actually-reachable instance of the same bug.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1694 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for dce_aux write reply buffer overflow 457 B view raw
VERDICT.md verdict Source-only verification verdict 824 B ↓ raw
build.sh build-script No-op (source-only) 109 B view raw
run.sh run-script No-op (source-only) 107 B view raw
VERDICT.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1694: dce_aux write reply buffer overflow

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

ctx->returned_byte (5-bit HW field max 31) set as reply.length; reply_data is 16 bytes -> OOB.

Source reference: sys/dev/drm/amd/display/dc/dce/dce_aux.c:690-691.

Reproduction

Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed. The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present on the QEMU/virtio guest. The finding is HW-gated.

Fix

Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC β€” rc=0, -Werror clean.

See fix.diff for the git-apply-able patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.

'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/drm/amd/display/dc/dce/dce_aux.c:690. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

Cap at DEFAULT_AUX_MAX_DATA_SIZE. Matches finding.

Verdict

REPRODUCED (source-confirmed). returned_byte (max 31) as reply.length; buffer is 16 -> OOB. Cited path verified at sys/dev/drm/amd/display/dc/dce/dce_aux.c:690. HW/module-gated on QEMU guest.