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

radeon_router_select_ddc_port/cd_port write an uninitialized kernel-stack byte into the DDC/CD router i2c mux registers when the router read fails (and leak it via DRM_DEBUG)

Summary

Both router-select helpers declare 'u8 val' uninitialized; radeon_i2c_get_byte writes *val only on success, but on NAK (broken mux, KVM/dock quirks) the callers unconditionally mask and write the garbage byte into router config registers 0x3/0x1 and print it to the debug log. Inherited verbatim from upstream - upstreamable fix. val=0 init (or a real return value from get_byte).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4445 Β· 6 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
df4445_probe.c β€” 809 B view raw
fix.diff β€” 750 B view raw
verdict.json β€” 2.0 KB view raw
manifest.json β€” 626 B view raw

DF-4445 evidence pack

  • What: radeon_router_select_ddc_port() / radeon_router_select_cd_port() use u8 val; uninitialized (sys/dev/drm/radeon/radeon_i2c.c:1108-1133, :1136-1161). radeon_i2c_get_byte() only writes *val when the i2c transfer succeeds (radeon_i2c.c:1077-1083); on failure the caller's val keeps its garbage stack contents and is then unconditionally masked/OR-ed and written back to the router's i2c config registers (0x3 / 0x1) β€” garbage in every bit outside ddc_mux_control_pin. The failure branch of radeon_i2c_get_byte() also DRM_DEBUG-prints the uninitialized byte. Upstream Linux has the same defect (inherited).
  • Severity: Low (needs a router-equipped connector + failing router read; consequence is wrong mux configuration / display misrouting and a debug-log stack-byte leak). Verification skipped per audit contract for Low findings; no radeon hardware on the audit guest anyway (virtio-gpu only).
  • Fix: initialize u8 val = 0; in both functions (or bail out when radeon_i2c_get_byte failed β€” it would need a return value for that; minimal safe fix is the zero-init).

Reproduce (on radeon hardware)

cc -O2 -o /tmp/df4445 df4445_probe.c && /tmp/df4445   # optional helper

The defect itself is visible only with drm.debug=0x02 while a DDC probe runs on a connector whose router read NAKs (broken/unplugged mux chip): dmesg shows i2c 0x.. 0x<garbage> read failed with a changing garbage byte derived from prior stack contents.

Files

  • VERDICT.md β€” static line-cited analysis
  • df4445_probe.c β€” optional compiled-but-inert demonstration stub
  • fix.diff β€” two-line zero-init fix
  • verdict.json (status untested), manifest.json
VERDICT.md
↓ download raw

DF-4445 β€” uninitialized val written into DDC/CD router mux registers when the router i2c read fails

Verdict

Status: untested (Low severity β€” verification skipped per audit contract; additionally no radeon hardware exists on the audit guest). Static analysis: certain.

Line-cited trace

  1. sys/dev/drm/radeon/radeon_i2c.c:1110 β€” u8 val; uninitialized (same in radeon_router_select_cd_port at :1138).
  2. radeon_i2c_get_byte(router_bus, i2c_addr, 0x3, &val) (radeon_i2c.c:1118-1120): its only write to *val is inside if (i2c_transfer(...) == 2) (radeon_i2c.c:1077-1079). On transfer failure β€” NAK from a missing/broken router chip β€” val keeps the previous stack contents. The failure branch also leaks that byte to the log: DRM_DEBUG("i2c 0x%02x 0x%02x read failed\n", addr, *val) (radeon_i2c.c:1081-1083).
  3. radeon_i2c.c:1121 β€” val &= ~ddc_mux_control_pin; β€” garbage survives in every bit not owned by control_pin.
  4. radeon_i2c.c:1122-1124 β€” the garbage-derived byte is written back to router register 0x3; the same pattern repeats for register 0x1 at :1125-1132 (val |= ddc_mux_state only constrains the mux bits).

Consequences: unpredictable corruption of the external i2c router's config registers (other bits of 0x3/0x1 control unrelated mux functions on those chips β€” display routing malfunctions), plus a one-byte kernel-stack disclosure into the debug log when drm.debug is enabled (log readable per sysctl policy).

Reachability: radeon_ddc_probe() calls radeon_router_select_ddc_port() (radeon_i2c.c:63-64) on every connector probe of a router-equipped connector (HPD poll / modeset). Trigger requires the router read to fail β€” i.e. a router chip that NAKs (common with KVM tricks/DP-mST docks/hardware faults) β€” while the subsequent put_byte of garbage goes out on the same dead bus (no effect on DF's stubbed bit-bang shim; real effect on working-hardware configurations, incl. upstream Linux which has the identical code).

Divergence note: this is not DF-specific code β€” verified byte-for-byte against upstream's radeon_i2c.c (the DF file's only functional divergences are the lockinit/i2c_add_adapter shim lines at :932 and :970-973). Filed because it is a real, fixable defect in the audited file (CWE-457, upstreamable).

Fix

u8 val = 0; in both functions (fix.diff). The stronger fix β€” make radeon_router_select_* bail when the read fails β€” needs an API change to radeon_i2c_get_byte (return int) and can be proposed upstream separately.

Why not runtime-verified

Low severity (contract: skip); no radeon hardware on the audit guest (virtio-gpu 0x1234:1111 only β€” see DF-4444/env.txt for the PCI inventory).

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

not_testable (Low-severity static finding; no radeon hw on guest)

fix.diff
↓ fix.diffper-fix-DF-4445

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md line-cited trace; fix.diff (zero-init)

PoC changes

n/a (static finding; helper stub only)

Verified recommended fix

Initialize u8 val = 0 in radeon_router_select_ddc_port/radeon_router_select_cd_port

Verdict

Static trace is complete and certain (see VERDICT.md): radeon_i2c.c:1110/:1138 declare 'u8 val' uninitialized; radeon_i2c_get_byte() only writes it on i2c success (radeon_i2c.c:1077-1083) but the callers unconditionally mask/OR and write the byte back into router config registers 0x3/0x1 (radeon_i2c.c:1121-1132, 1149-1160) and the failure branch DRM_DEBUG-prints the uninitialized byte. Inherited verbatim from upstream Linux (not DF-specific code). Impact ceiling: wrong external-router configuration bits (display misrouting) + one-byte kernel-stack disclosure into debug logs; Low. Verification skipped per audit contract for Low findings; no radeon hardware exists on the audit guest regardless.