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

amdgpu_dm debugfs: OR-combined valid_input accepts invalid DP lane_count; stored unvalidated and consumed by a lane_settings[LANE_COUNT_DP_MAX=4] write loop -> stack OOB write (LATENT: file not compiled)

Summary

dp_link_settings_write validates lane_count and link_rate with ONE flag - 'echo 5 a' passes with an invalid lane_count, stored verbatim into preferred_link_setting (no validation), and the live consumer returns it verbatim into link training. dp_phy_settings_write then loops r<lane_count writing lane_settings[r] past the [4]-element array - 12 bytes/extra lane, unbounded linear stack smash for 0xffffffff. LATENT: the file is doubly dead (not in SRCS AND CONFIG_DEBUG_FS undefined). Guest-compiled 1:1 logic demo reproduces ACCEPTED/stored/iteration-r=4-OOB. The cross-file amdgpu_dm_color.c:260 lead was verified REAL but is already DF-2083 (not re-filed). Split-the-flag fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4621 Β· 6 files
FileTypeDescriptionSize
dp_link_logic_demo.c β€” 4.3 KB view raw
demo_run.log β€” 476 B view raw
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 3.7 KB view raw

DF-4621 β€” amdgpu_dm debugfs: OR-combined valid_input accepts invalid lane_count β†’ stack OOB write in dp_phy_settings_write (LATENT: file not compiled)

What

dp_link_settings_write() validates the two user parameters with a single OR-combined flag (sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c:195-221):

    switch (param[0]) {          /* lane_count */
    case LANE_COUNT_ONE: ... valid_input = true; ...
    switch (param[1]) {          /* link_rate */
    case LINK_RATE_LOW: ... valid_input = true; ...   /* same flag! */
    if (!valid_input) { ... return; }

echo "5 a" > link_settings (or echo ffffffff a) passes with an invalid lane_count because the link_rate matched. The value is then stored unvalidated into link->preferred_link_setting by dc_link_set_preferred_link_settings() (dc/core/dc.c:484), and consumed:

  1. dp_phy_settings_write (:386-410) β€” when test_pattern_enabled, link_lane_settings.link_settings.lane_count = link->preferred_link_setting.lane_count, then for (r = 0; r < lane_count; r++) link_lane_settings.lane_settings[r] = … writes past lane_settings[LANE_COUNT_DP_MAX = 4] (include/link_service_types.h:71-75; LANE_COUNT_DP_MAX = LANE_COUNT_FOUR, dc/dc_dp_types.h:37). lane_count=5 β†’ 12-byte stack overrun; lane_count=0xffffffff β†’ unbounded linear stack smash (values constrained to enum triples 0..3 per 4-byte slot). link_lane_settings is a local β€” stack OOB write (CWE-787).
  2. decide_link_settings() (dc/core/dc_link_dp.c:1517-1525, live code) returns *link_setting = link->preferred_link_setting verbatim β†’ invalid lane_count propagates into link training (lt_settings.lane_settings[] walks, DPCD lane writes).

Liveness (verified this run β€” honest status)

The file is doubly dead in DragonFly: it is absent from sys/dev/drm/amdgpu/Makefile SRCS (lines 195-201 list amdgpu_dm.c, amdgpu_dm_irq.c, amdgpu_dm_mst_types.c, amdgpu_dm_color.c, services, helpers, pp_smu β€” not this file), and its only two callers are compiled under #if defined(CONFIG_DEBUG_FS) (amdgpu_dm.c:478-479 dtn_debugfs_init, :4119-4120 connector_debugfs_init), which is not defined (sys/dev/drm/kconfig.h). So today this is an enable-day hazard (turning on debugfs builds without also fixing validation), consistent with DF-1764/DF-1765/DF-4554 precedent. The guest also has no AMD GPU (pciconf: no AMD devices).

What runs today

dp_link_logic_demo.c transcribes the validation logic (:195-221) and the consumer loop (:394-410) 1:1 and runs on the guest β€” gcc itself flags the OOB iteration (warning: iteration 4 invokes undefined behavior):

input: lane_count=5 (invalid), link_rate=0xa (valid)
ACCEPTED by the single valid_input flag (amdgpu_dm_debugfs.c:195-221)
stored preferred_link_setting.lane_count = 5 (dc.c:484, unvalidated)
loop iteration r=4: OOB write at &lane_settings[4] -- past lane_settings[LANE_COUNT_DP_MAX-1=3]

(demo_run.log; a transcription logic-proof, NOT a kernel run β€” see VERDICT.md)

Fix

fix.diff splits the flag into valid_lane_count / valid_link_rate and requires both (if (!valid_lane_count || !valid_link_rate)).

VERDICT.md
↓ download raw

DF-4621 VERDICT

Finding

OR-combined valid_input in dp_link_settings_write (amdgpu_dm_debugfs.c:195-221) accepts an invalid lane_count whenever the link_rate is valid; the lane_count is stored unvalidated (dc/core/dc.c:484) and later drives: - the lane_settings[r] write loop in dp_phy_settings_write (amdgpu_dm_debugfs.c:403) β€” array is lane_settings[LANE_COUNT_DP_MAX=4] (link_service_types.h:71-75, dc_dp_types.h:37) β†’ stack OOB write (12 bytes per extra lane; unbounded for lane_count=0xffffffff); - decide_link_settings (dc_link_dp.c:1517-1525) β€” verbatim propagation into link training (LIVE dc code; its only corrupting producer is this dead debugfs file).

Status: untested (kernel path), logic-proven (guest demo)

  • The kernel path CANNOT run on this guest for two independent reasons: 1. amdgpu_dm_debugfs.c is not compiled in DragonFly: absent from sys/dev/drm/amdgpu/Makefile SRCS (:195-201); callers #if'd under CONFIG_DEBUG_FS (amdgpu_dm.c:478,4119) which kconfig.h does not define. 2. The guest has no AMD GPU (env.txt: no AMD PCI devices) β€” amdgpu DM never attaches.
  • Supporting runtime logic-proof on the guest (dp_link_logic_demo.c, transcription of :195-221 and :394-410 with the real constants): input "5 a" β†’ ACCEPTED, lane_count=5 stored, consumer loop performs iteration r=4 writing past lane_settings[3] β€” gcc corroborates with -Waggressive-loop-optimizations: iteration 4 invokes undefined behavior (demo_run.log). This demonstrates the validation flaw at runtime but is NOT a kernel execution; verdict status remains untested honestly.

Exploitability (if the file is ever built, i.e. enable-day)

Preconditions: AMD display hardware + CONFIG_DEBUG_FS build + the file added to SRCS + root-writable... actually /sys/kernel/debug/dri files are 0644 (:712) β€” world-writable from the DRM master's context; debugfs is root-only mounted (debugfs root, 0700 typically) β†’ effectively a root-or-console trigger, EXCEPT the invalid lane_count persists in link->preferred_link_ setting and decide_link_settings (live path) then walks it during unprivileged-triggerable modesets. Even then written values are enum triples {0..3} per 4-byte slot β†’ crash-grade corruption, not clean pointer control; lane_count=0xffffffff gives an unbounded low-value stack smash β†’ panic. Severity kept at Medium because it is latent (project precedent: dead-code findings DF-1764/1765/DF-4554 = Low; this one is an OOB write primitive, flagged for anyone enabling the debugfs build).

Fix

fix.diff (mechanically generated, applies to the tree as-is): split valid_lane_count / valid_link_rate, require both.

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 is the standard upstream-style split (valid_lane_count && valid_link_rate); cannot be kernel-validated on this guest (file not compiled, no AMD GPU). The logic demo rejects lane_count=5 under the split by construction.

fix.diff (mechanically generated, clean apply); demo validates the rejection branch when the flag is split.
↓ fix.diffper-fix-DF-4621

Confirmed kernel references

Detail

Evidence (decisive lines)

demo_run.log (guest): 'ACCEPTED by the single valid_input flag', 'stored preferred_link_setting.lane_count = 5 (dc.c:484, unvalidated)', 'loop iteration r=4: OOB write at &lane_settings[4] -- past lane_settings[LANE_COUNT_DP_MAX-1=3]' plus gcc -Waggressive-loop-optimizations warning; README/VERDICT cite the dead-build proof (Makefile:195-201, amdgpu_dm.c:478,4119).

PoC changes

no kernel PoC possible (file not in build; no AMD GPU); wrote a 1:1 transcription logic demo (dp_link_logic_demo.c) compiled and run on the guest instead

Verified recommended fix

Split valid_input into valid_lane_count and valid_link_rate and require both before storing preferred link settings (see fix.diff).

Verdict

Kernel path UNTESTABLE on this guest: amdgpu_dm_debugfs.c is not compiled (absent from sys/dev/drm/amd/amdgpu/Makefile SRCS :195-201; callers #if defined(CONFIG_DEBUG_FS) at amdgpu_dm.c:478/:4119, undefined in kconfig.h) and the guest has no AMD GPU (no AMD PCI devices). Source-level analysis is certain: the single OR-combined valid_input flag (amdgpu_dm_debugfs.c:195-221) accepts an invalid lane_count whenever the link_rate parses; it is stored unvalidated (dc.c:484) and consumed by dp_phy_settings_write's lane_settings[LANE_COUNT_DP_MAX=4] write loop (:403 β€” stack OOB write; unbounded linear smash for lane_count 0xffffffff) and by the LIVE decide_link_settings (dc_link_dp.c:1523 verbatim propagation into link training). Supporting runtime logic-proof ran on the guest: the transcribed validation accepts '5 a', stores lane_count=5, and the consumer loop performs OOB iteration r=4 β€” gcc itself warns 'iteration 4 invokes undefined behavior' (demo_run.log). Latent enable-day hazard; fix.diff splits the two flags.