get_bytes_per_element() returns 0 for dm_444_8/dm_mono_8 and unhandled enum values -> integer divide-by-zero (#DE kernel panic) at get_surf_rq_param():641 for linear surfaces, dead-code #DE at :745, and dml_log2(0)=-127 UB-shift garbage programming
Summary
The switch has no case for the legal enum value dm_444_8/dm_mono_8 nor out-of-set values, returning 0. With bpe==0: LINE integer division at :641 (linear branch; read downstream so -O2 cannot eliminate), a second dead-code #DE at :745, and dml_log2(0)=-127 making all 1<<log2/*>>log2* expressions UB shifts producing garbage - even the non-crashing tiled path silently programs nonsense RQ/DLG registers. Latent: the sole caller maps default->dm_444_32 today; armed the moment any caller passes the 8-bit mono class. Harness compiles the REAL unmodified file with realistic DCN1 config: mono8_linear SIGFPE 3/3, normal + fixed complete. Handle-format + zero-guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4198 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 5.9 KB | β raw | |
| dml1_harness.c | β | 4.3 KB | view raw | |
| dm_services.h | β | 1.2 KB | view raw | |
| dml1_display_rq_dlg_calc.fixed.c | β | 66.2 KB | view raw | |
| build.sh | β | 168 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 537 B | view raw | |
| run.log | β | 1.3 KB | view raw | |
| run.2.log | β | 39 B | view raw | |
| run.3.log | β | 1.6 KB | view raw | |
| env.txt | β | 419 B | view raw | |
| fix.diff | β | 1009 B | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-4198 β get_bytes_per_element() returns 0 for dm_444_8/dm_mono_8 and unhandled formats β integer divide-by-zero (#DE) in get_surf_rq_param + dml_log2(0)=-127 UB-shift family
File under audit: sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c (pass 2)
What
get_bytes_per_element() (dml1_display_rq_dlg_calc.c:40-65) handles only
dm_444_16/32/64 and dm_420_8/10. The legal enum value
dm_444_8 (= dm_mono_8, display_mode_enums.h:42-43) β and any out-of-set
enum β yields ret_val = 0. With bytes_per_element == 0 the file then:
- live #DE:
get_surf_rq_param()line 641, linear surfaces:blk256_width = 256 / bytes_per_element;β integer division by zero β kernel #DE β panic (SIGFPE in the model harness). - dead-code #DE (source-level): line 745
meta_blk_width = meta_blk_bytes * 256 / bytes_per_element / meta_blk_height;βmeta_blk_widthis never read, so gcc -O2 eliminates it (harness confirmed: tiled mono8 does NOT trap), but the source division is still against the same zero. - UB shift family:
dml_log2(0)returns -127 (dcn_bw_log bit-trick, calcs/dcn_calc_math.c:112-126: log2f-mantissa approximation of 0.0 gives -127), so every downstream1 << log2_*/>> log2_*(lines 636/652-653/678/684/708-709/719/736-739/739) andlog2_xxxarithmetic gets -127-based garbage; even non-linear mono8 silently programs wrong RQ/DLG registers (harness: sw=1919 h=2, stored_swath=0 β nonsense for a 1-byte format).
Reachability (honest)
The only in-tree caller of dml1_rq_dlg_get_rq_params is
dcn_bw_calc_rq_dlg_ttu() (calcs/dcn_calcs.c:487), and its format mapping
(calcs/dcn_calcs.c:353-377) maps VIDEO_420βdm_420_8/10,
ARGB16161616βdm_444_64, everything elseβdm_444_32 β it never produces
dm_444_8 today. The defect is therefore latent*: one new format mapping
(or a DCN1 caller passing dm_mono_8, which the enum explicitly defines as an
alias) turns it into a kernel panic on modeset validation. Additionally all
of it sits behind DCN1 (Raven) hardware. Arithmetic certainty, trigger
currently absent β hence Low severity.
How it is proven here
dml1_harness.c compiles the REAL, unmodified
dml1_display_rq_dlg_calc.c (plus real dcn_calc_math.c,
dml_common_defs.c, real headers; only dm_services.h is a stub providing
the non-fatal WARN_ON-style ASSERT, matching os_types.h:76 release semantics)
on the DragonFly guest and drives it with a realistic DCN1 config
(1920x1080, det_buffer 164 KB, vmm_page 4096):
$ ./dml1_harness normal # dm_444_32 (what dcn_calcs maps) β OK, sane regs
$ ./dml1_harness mono8 # dm_444_8, tiled β NO trap (line 745 is dead
# code at -O2) but silently WRONG values
$ ./dml1_harness mono8_linear # dm_444_8 + dm_sw_linear β SIGFPE at :641
Floating point exception (core dumped) rc=136 (3/3 runs)
dml1_harness_fixed is the SAME harness compiled with fix.diff applied to
the real dml1 file: all three modes complete rc=0 and mono8 now produces
sane values (bpe=1).
Reproduce
./build.sh # copies the real files from /usr/src, builds baseline+fixed ./run.sh # baseline mono8_linear -> SIGFPE; fixed -> clean
Fix
fix.diff: (1) handle dm_444_8 β 1 byte/element in
get_bytes_per_element (mono 8bpc is 1 byte per element, matching the enum
alias); (2) defense-in-depth: bail out of get_surf_rq_param when
bytes_per_element == 0 (ASSERT is only WARN_ON in release builds, so the
explicit return is what prevents the fault).
DF-4198 VERDICT
Status: inconclusive (hardware-gated: guest has no AMD/DCN GPU, and the in-tree caller never passes dm_444_8 today) β the divide-by-zero itself is proven EXACTLY on the DragonFly guest by compiling and running the REAL, unmodified dml1_display_rq_dlg_calc.c with crafted inputs; the fault is a SIGFPE (userland #DE analog). Impact class if reached in-kernel: integer divide exception β kernel panic (DoS); also silent wrong register programming for non-linear unhandled formats. Not memory corruption; uid=0 chain N/A.
Root cause (path:line)
sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c:40-65βget_bytes_per_element()has no case fordm_444_8/dm_mono_8(display_mode_enums.h:42-43) nor for out-of-set values β returns 0.:641βblk256_width = 256 / bytes_per_element;(surf_linear branch) β live integer division by zero (blk256_width is read at :652/:676/:684/ :688-695, so gcc -O2 cannot remove it β confirmed: harness traps).:745βmeta_blk_width = meta_blk_bytes * 256 / bytes_per_element / meta_blk_height;β second zero divisor in source; dead code at -O2 (meta_blk_widthnever read β eliminated; harness proves tiled mono8 does NOT trap), andmeta_blk_height = blk256_height * 64is 0 as well for bpe=0 (get_blk256_size:77-95 leaves 0 for unknown bpe).dml_log2(0) == -127(calcs/dcn_calc_math.c:112-126 bit-trick) βlog2_bytes_per_element = -127(:636) poisons1 << β¦/>> β¦expressions (UB shifts; x86 masks the count, producing garbage rather than a fault) β this is why even the non-crashing paths compute nonsense (harness mono8: sw=1919 h=2 stored_swath=0).
Reachability analysis (what keeps this Low)
- Sole caller:
dcn_bw_calc_rq_dlg_ttu()at calcs/dcn_calcs.c:487, fed bypipe_ctx_to_e2e_pipe_params()whose format switch (calcs/dcn_calcs.c:353) maps VIDEO_420_βdm_420_8/10, ARGB16161616βdm_444_64, defaultβdm_444_32.dm_444_8is never produced today; the enum aliasdm_mono_8exists precisely because the format class is part of the DML input vocabulary, so any future mapping addition (upstream DCN reworks, new callers) arms the panic. - Everything sits behind DCN1 (Raven) hardware: dcn_calcs runs during dcn10_validate_bandwidth; no AMD GPU can be attached to this guest (ls /dev/dri β ENOENT).
Trigger when armed: modeset validation with an 8-bit mono/444 plane in linear layout β #DE in task context β panic.
Reproduction (guest, DragonFly 6.5-DEVELOPMENT, cc 8.3, -O2)
Baseline (real, unmodified file from sys/):
$ ./dml1_harness normal # source_format=dm_444_32 OK: rq_l sw=1928 h=8 dpte_row_h=128 meta_row_h=64 ... stored_swath=123392 (assert-warns=0 dtrace=13) rc=0 $ ./dml1_harness mono8 # dm_444_8, tiled β no trap (dead :745), wrong values OK: rq_l sw=1919 h=2 dpte_row_h=32 ... stored_swath=0 ... rc=0 $ ./dml1_harness mono8_linear # dm_444_8 + dm_sw_linear β LIVE :641 division Floating point exception (core dumped) rc=136 (3/3 runs, run.2.log)
Patched (fix.diff applied to the same real file, dml1_display_rq_dlg_calc.fixed.c):
$ ./dml1_harness_fixed normal rc=0 (identical regs to baseline normal) $ ./dml1_harness_fixed mono8 rc=0 sw=1936 h=16 dpte_row_h=256 ... stored_swath=61952 (sane 1-byte-format values) $ ./dml1_harness_fixed mono8_linear rc=0 sw=2304 h=1 dpte_row_h=128 ... stored_swath=4608
The fixed binary's mono8* outputs are plausible for bpe=1 (swath width
doubles vs bpe=4? no β narrows in bytes; the printed sanity: h=16/h=1 are
power-of-two swath heights as required).
Killed negatives (dce_calcs checklist applied to this file)
- Divisions by mode/socBB clocks:
t_calc_us = 24/min_dcfclk_mhz(:1146),line_time_in_us = htotal / pclk(:1206), delay ratios (:1231-1232),line_wait / line_time(:1293),prefetch_bw = ... / t_pre_us(:1392),flip_bw / total_flip_bytes(:1393-1394) β all double divisions; a zero/NaN operand yields inf/NaN β(unsigned int)cast β 0x80000000-style garbage (cvttsd2si), NOT a trap. No #DE from the double sites. / vratio_l/c(:1546/:1550),/ dpte_groups_per_row_ub(:1518/:1523),/ meta_chunks_per_row_ub(:1528), hratios_cur0 (:1840) β doubles, same as above.data_pitchdivisions (:505/:832): data_pitch comes frompipe->plane_res.scl_data.viewport.width(calcs/dcn_calcs.c:285), i.e. a validated non-degenerate plane viewport, not a raw UABI pitch; zero would require a 0-width visible plane, which atomic validation rejects β speculative only, not filed.- Array indexing: the file contains no arrays β struct walks only; no OOB writes (tq/surf structs are fixed-size members).
- u32 fixed-point accumulation overflow (:305/:331/:727/:1366-1383): bounded by validated mode dimensions (viewport β€ 16k, bpe β€ 8, lsw β€ ~64) β max ~10^8, no overflow.
- meta_blk_width dead store +
dml_round_to_multiple(...)/64.0truncation (:261-266): logic quirks upstream-inherited, no fault.
Fix validation
fix.diff (sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c):
adds the missing dm_444_8β1 mapping and an explicit bpe==0 bail-out in
get_surf_rq_param (ASSERT alone is WARN_ON in release builds, os_types.h:76).
Validated end-to-end on the REAL source file in the harness: baseline
mono8_linear SIGFPE 3/3 vs fixed 0/3 with sane outputs. Kernel-level build
is not possible on this guest (no AMD hardware to load amdgpu DC; building
the amdgpu module is possible but cannot be exercised), so
fix_status: inconclusive β semantic validation is at real-source model
level.
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
inconclusivefix.diff applied to the REAL dml1_display_rq_dlg_calc.c and recompiled in the harness: baseline mono8_linear SIGFPE 3/3 vs fixed 0/3 with sane outputs (sw=2304 h=1 dpte_row_h=128); normal-mode outputs byte-identical to baseline. Kernel-level validation not possible on this guest (no AMD GPU to load the amdgpu DC path), so kernel-level fix status stays inconclusive; real-source-level validation passed cleanly.
run.log/run.3.log baseline-vs-patched contrast lines; dml1_display_rq_dlg_calc.fixed.c (shipped, = fix.diff applied); fix.diff
Confirmed kernel references
- sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c:40
- sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c:641
- sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c:745
- sys/dev/drm/amd/display/dc/dml/dml1_display_rq_dlg_calc.c:636
- sys/dev/drm/amd/display/dc/dml/display_mode_enums.h:42
- sys/dev/drm/amd/display/dc/calcs/dcn_calc_math.c:112
- sys/dev/drm/amd/display/dc/calcs/dcn_calcs.c:353
- sys/dev/drm/amd/display/dc/calcs/dcn_calcs.c:487
- sys/dev/drm/amd/display/dc/os_types.h:76
Detail
Evidence (decisive lines)
run.log baseline mono8_linear 'Floating point exception' rc=136 vs fixed mono8_linear 'OK: rq_l sw=2304 h=1 ... stored_swath=4608' rc=0; run.2.log 3/3 determinism; run.3.log end-to-end pack run (build.sh+run.sh) against the guest's /usr/src
PoC changes
harness written fresh (no seed): REAL kernel sources compiled unmodified; dm_services.h stub supplies DC_LOG_DML no-op and non-fatal ASSERT (os_types.h:76 release semantics: WARN_ON); DCN1-realistic ip/soc params (det_buffer 164KB, dpte_buffer 64 pte reqs, vmm_page 4096, 1920x1080 viewport)
Verified recommended fix
Handle dm_444_8 (dm_mono_8) -> 1 byte/element in get_bytes_per_element and bail out of get_surf_rq_param when bytes_per_element==0 (ASSERT is only WARN_ON in release builds; see fix.diff)
Verdict
Hardware-gated (guest has no AMD/DCN GPU) AND currently mapping-gated (the sole caller, calcs/dcn_calcs.c:353-377, maps every non-420/64bpp UABI format to dm_444_32 and never emits dm_444_8), so the in-kernel panic is not reachable today β but the arithmetic defect is certain and was proven EXACTLY on the DragonFly guest by compiling the REAL, unmodified dml1_display_rq_dlg_calc.c (plus real dcn_calc_math.c/dml_common_defs.c/DML headers from the guest's /usr/src, only dm_services.h stubbed to release-build WARN_ON semantics) and driving it with a realistic DCN1 config: source_format=dm_444_8 (the enum value aliased dm_mono_8, display_mode_enums.h:42-43) plus dm_sw_linear executes blk256_width = 256 / bytes_per_element at dml1_display_rq_dlg_calc.c:641 with bytes_per_element==0 -> integer division by zero -> SIGFPE (rc=136, 3/3 runs), the userland analog of the kernel #DE panic. Second zero-divisor at :745 is dead code at -O2 (meta_blk_width unused) and dml_log2(0)==-127 (dcn_bw_log bit-trick) silently programs garbage RQ/DLG registers for non-linear unhandled formats (harness mono8: stored_swath=0, nonsense). Patched real file (fix.diff) : all three modes rc=0 with sane 1-byte-per-element values. Same verification convention as the DF-3869 dce_calcs #DE family.
No comments yet.