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

transmitter_control_v1_6 applies 16-bit deep-color conversions to the 32-bit ulSymClock field β€” truncated transmitter symbol clock for HDMI deep color > 655.35 MHz

Summary

DIG_TRANSMITTER_CONTROL_PARAMETERS_V1_6.ulSymClock is ULONG (32-bit, 10KHz units), but the HDMI deep-color ratio block (copy-pasted from v1_5 where the field is USHORT) wraps it in le16_to_cpu/cpu_to_le16 - truncating input and result to 16 bits. Any deep-color-corrected 10KHz value > 65535 is wrong: guest replica shows 4/5 realistic modes corrupt (4K60 10bpc 74250->8714, 12bpc 89100->23564, 16bpc 118800->53264). Truncated value handed to the VBIOS. Also omits cpu_to_le32 on assignment. No memory-safety impact; display corruption/link-training failure. 32-bit-conversion fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4048 Β· 9 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
symclock_trunc.c β€” 2.7 KB view raw
build.log β€” 11 B view raw
run.log β€” 1.4 KB view raw
env.txt β€” 188 B view raw
fix.diff β€” 1.2 KB view raw
manifest.json β€” 954 B view raw
verdict.json β€” 3.0 KB view raw

DF-4048 β€” transmitter_control_v1_6 applies 16-bit deep-color math to the 32-bit ulSymClock field

What

sys/dev/drm/amd/display/dc/bios/command_table.c:857 and :869-890 (transmitter_control_v1_6): the V1_6 transmitter parameter struct's symbol clock is a 32-bit ULONG (DIG_TRANSMITTER_CONTROL_PARAMETERS_V1_6, sys/dev/drm/amd/include/atombios.h:1559-1575, "ULONG ulSymClock; // Symbol Clock in 10Khz"), but the HDMI deep-color ratio code β€” copy-pasted from transmitter_control_v1_5, where the field is a USHORT usSymClock β€” uses the 16-bit helpers on it:

params.ulSymClock = cntl->pixel_clock/10;                 /* :857 - also no cpu_to_le32 */
...
case COLOR_DEPTH_101010:
        params.ulSymClock =
                cpu_to_le16((le16_to_cpu(params.ulSymClock) * 30) / 24);  /* :873-874 */
case COLOR_DEPTH_121212:   ... * 36 / 24 ...               /* :877-878 */
case COLOR_DEPTH_161616:   ... * 48 / 24 ...               /* :881-882 */

le16_to_cpu(u32) truncates the input to 16 bits and cpu_to_le16(...) truncates the result to 16 bits. Any symbol clock whose 10 KHz value overflows 65535 is computed wrong β€” which includes common HDMI deep-color modes (4K@60 10bpc needs 74250; 12bpc 89100; 16bpc 118800 β€” all > 65535). The wrong ulSymClock is then handed to the VBIOS via EXEC_BIOS_CMD_TABLE (:892), programming a corrupted transmitter symbol clock (display artifacts / link training failure / black screen on the affected mode). :857 also skips the endian conversion entirely (moot on LE x86-64, wrong on BE).

Reproduce (guest, arithmetic replica)

cc -O2 -Wall -o symclock_trunc symclock_trunc.c
./symclock_trunc; echo EXIT=$?     # exits 1 (defect present)

Expected output (run.log)

case                          in(10KHz)      correct     produced      ok?
1080p60   8bpc (safe zone)        14850        14850        14850      yes
4K60     10bpc x30/24             59400        74250         8714    TRUNC
4K60     12bpc x36/24             59400        89100        23564    TRUNC
4K60     16bpc x48/24             59400       118800        53264    TRUNC
HDMI1.4 4K30 16bpc x48/24         34000        68000         2464    TRUNC

expected: all cases compute correctly; observed: 4/5 truncated
(also: :857 assigns without any cpu_to_le32 conversion - moot on LE x86, wrong on BE)
EXIT=1

4/5 realistic deep-color cases produce a wrong 32-bit field value from the kernel expression; only the sub-655.35 MHz 8bpc case survives.

VERDICT.md
↓ download raw

DF-4048 VERDICT β€” 16-bit deep-color math on 32-bit ulSymClock (transmitter_control_v1_6)

Reproduced?

Yes, at the arithmetic level β€” the exact kernel expression (cpu_to_le16((le16_to_cpu(params.ulSymClock) * N) / 24) on a ULONG field) was replicated 1:1 in symclock_trunc.c and executed on the guest: 4/5 realistic HDMI deep-color symbol clocks come out wrong (see run.log; exit 1). The downstream display impact (corrupted symbol clock handed to VBIOS at command_table.c:892) is hardware-gated β€” the guest has no AMD GPU, and v1_6 transmitter commands apply to DCE11/12-class parts (Polaris-era) β€” so the persisted impact is none with the defect itself proven certain.

Root cause (path:line)

  • command_table.c:857 β€” params.ulSymClock = cntl->pixel_clock/10; assigned with no cpu_to_le32 (moot on LE x86-64, wrong on BE).
  • command_table.c:869-890 β€” deep-color ratio block uses le16_to_cpu/cpu_to_le16 (16-bit conversions) on the 32-bit ulSymClock field: copy-paste from transmitter_control_v1_5 (command_table.c:811-828), where the field is USHORT usSymClock (atombios.h:1477-1493). Both the input and the result are truncated to 16 bits.
  • Struct: atombios.h:1559-1575 β€” ULONG ulSymClock; // Symbol Clock in 10Khz β€” the deep-color-corrected value (e.g. 118800 for 4K@60 16bpc) legitimately exceeds 65535, so the truncation bites on real modes, not just laboratory ones.

Impact

Wrong transmitter symbol clock programmed for HDMI deep-color modes with effective clocks > 655.35 MHz on v1_6-transmitter ASICs β†’ display corruption/link instability (local DoS of the display output, no memory safety impact, no info leak). Severity Low (specific hardware + specific modes).

Fix

Use the 32-bit conversions in v1_6 (and convert the initial assignment):

-   params.ulSymClock = cntl->pixel_clock/10;
+   params.ulSymClock = cpu_to_le32(cntl->pixel_clock / 10);
 ...
-       params.ulSymClock =
-           cpu_to_le16((le16_to_cpu(params.ulSymClock) * 30) / 24);
+       params.ulSymClock =
+           cpu_to_le32((le32_to_cpu(params.ulSymClock) * 30) / 24);

(same for the 36/24 and 48/24 cases; fix.diff carries the full hunk).

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

Kernel-build validation hardware-gated (no AMD GPU). fix.diff is a mechanical 16->32-bit conversion swap; the arithmetic replica's 'correct' column implements exactly the patched math (all 5 cases then compute correctly).

findings/poc/DF-4048/fix.diff; run.log 'correct' column = post-fix expected values
↓ fix.diffper-fix-DF-4048

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (4/5 TRUNC rows incl. 74250->8714, 89100->23564, 118800->53264, 68000->2464; EXIT=1), VERDICT.md, atombios.h struct quote in README.md

PoC changes

n/a (authored fresh: exact-expression userland replica; kernel-side field is 32-bit so the le16 helpers truncate on both input and output)

Verified recommended fix

Use cpu_to_le32/le32_to_cpu for ulSymClock in transmitter_control_v1_6 (both the :857 assignment and the three deep-color cases); see fix.diff

Verdict

Defect reproduced at the arithmetic level on the guest: symclock_trunc.c replicates transmitter_control_v1_6's deep-color expressions 1:1 (cpu_to_le16((le16_to_cpu(params.ulSymClock)*N)/24) on the 32-bit ULONG ulSymClock, atombios.h:1559-1575) and shows 4/5 realistic HDMI deep-color clocks computed wrong (e.g. 4K60 16bpc: correct 118800, produced 53264; run.log, exit 1). The truncated ulSymClock is handed to the VBIOS via EXEC_BIOS_CMD_TABLE (command_table.c:892), programming a corrupted transmitter symbol clock -> display corruption/link failure for >655.35MHz deep-color modes on v1_6-transmitter DCE11/12 ASICs. Display-level manifestation is hardware-gated (no AMD GPU in guest), hence impact recorded as none; no memory-safety consequence (bounded field write inside the params struct). Also noted: :857 assigns ulSymClock without cpu_to_le32 (BE-only issue).