Undefined shift counts throughout the ATOM interpreter: VBIOS-controlled shift operands (up to 32-bit) used unmasked in shl/shr/shift ops, WS OR/AND masks and IIO masks
Summary
The shift ops use full 32-bit VBIOS sources as shift counts; the WS masks shift by u8 values; the IIO masks compute >>(32-width) with width==0. C11 UB; guest gcc demonstrates compiler-dependent divergence (0xffff<<40 folds to 0 at compile time while runtime >>32 is masked). No memory-safety consequence (hardening). atom_shl/shr helper fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4340 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| atom_harness.c | β | 7.5 KB | view raw | |
| run.sh | β | 101 B | view raw | |
| run_cases.log | β | 507 B | view raw | |
| env.txt | β | 467 B | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| verdict.json | β | 2.1 KB | view raw |
DF-4340 β Undefined shift counts throughout the ATOM interpreter (VBIOS-controlled)
Severity: Low Confidence: certain File: sys/dev/drm/radeon/atom.c
What
Shift counts are untrusted VBIOS values and are used without masking; counts
= 32 are undefined behaviour (C11 6.5.7):
atom_op_shl(atom.c:937):dst <<= shift;βshiftis a full 32-bit ATOM source operand (any value 0..0xFFFFFFFF);atom_op_shr(atom.c:956):dst >>= shift;β same;atom_op_shift_left(atom.c:904) /atom_op_shift_right(atom.c:920):shiftis a VBIOS byte (0..255);- WS masks (atom.c:252/255):
1 << gctx->shiftwithgctx->shifta VBIOS-written uint8_t (0..255); - IIO masks (atom.c:125,131,137,147,157):
0xFFFFFFFF >> (32 - CU8(base+1))β width byte 0 gives>> 32;(index|data|io_attr) >> CU8(base+2)β byte shift counts up to 255.
This is not a memory-safety bug by itself: on x86 the counts are masked
mod 32 at runtime β when the compiler emits a runtime shift. The guest
demo shows gcc 8.3 -O1 instead constant-folds one instance to 0
(0xffff << 40 β 0x00000000), i.e. the computed GPU programming values
are compiler-dependent garbage for out-of-range counts. Hardening with
defined semantics (>= width β 0) makes behavior portable and predictable.
Reproduce (logic level)
cc -O1 -g -o atom_harness atom_harness.c && ./atom_harness shift # -> (0xffff << 40)=0x00000000 vs (0xffff << 8)=0x00ffff00 -> divergent # IIO mask width=0: >>32 gives 0x12345678 (UB relied upon)
Fix
fix.diff (git-apply checked): atom_shl/atom_shr helpers implementing
defined semantics, applied to the four shift opcodes and the two WS masks.
(The IIO mask sites are inside atom_iio_execute and can use the same
helpers; left as noted because that function is also touched by DF-4337.)
DF-4340 VERDICT
Status: inconclusive (hardening-class UB; logic-level divergence demonstrated on the audit guest). Defect: certain (UB), impact low.
Root cause
C11 6.5.7: shifting a 32-bit value by >= 32 (or by a negative count) is
undefined. The ATOM interpreter uses VBIOS-controlled counts directly:
atom_op_shl/shr (atom.c:937/956, 32-bit operand), atom_op_shift_left/right
(atom.c:904/920, byte operand), WS OR/AND masks (atom.c:252-255, u8),
IIO masks (atom.c:125,131,137,147,157 β >> (32 - width) with width=0
gives >> 32, plus byte-count shifts of index/data/io_attr).
Reproduction (guest, gcc 8.3 -O1 β run_cases.log shift case)
(0xffff << 40)compiled to0x00000000while(0xffff << 8)gives0x00ffff00β the compiler exploited the UB (constant-folded to 0), so the value the interpreter programs into GPU registers for out-of-range VBIOS shift counts is compiler-dependent, not the x86 "masked" behavior often assumed.- IIO mask width=0 (
>> 32) yielded the unmasked value (x86 runtime masking), i.e. a different result than the folded case for the same class of UB.
No memory-safety consequence (results are consumed as register values); classified Low / hardening. Kernel runtime path itself is hardware-gated (no ATI GPU on guest, env.txt).
Fix validation
fix.diff defines atom_shl/atom_shr (>= width β 0) and applies them at the
six primary sites; git apply --check OK. Logic re-check: helpers produce
deterministic 0 for out-of-range counts by construction (no kernel build
possible on the guest β fix_status inconclusive).
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 git apply --check OK; helpers make out-of-range counts deterministically 0 by construction. Kernel-runtime validation impossible on this guest.
fix.diff (helpers + 6 call-site replacements)
Confirmed kernel references
Detail
Evidence (decisive lines)
run_cases.log [shift] shows folded 0x00000000 vs runtime-masked 0x12345678 divergence for the same UB class
PoC changes
no seed
Verified recommended fix
atom_shl/atom_shr helpers with defined semantics (count >= 32 => 0), applied to the shift opcodes, WS masks and IIO masks
Verdict
UB certain, impact low (hardening): ATOM shift counts are untrusted VBIOS values used unmasked at atom.c:937/956 (32-bit operand), 904/920 (byte), 252-255 (WS masks, u8) and in the IIO masks at 125/131/137/147/157 (>> (32-width)). Guest gcc 8.3 -O1 demonstrates the divergence concretely: 0xffff<<40 constant-folds to 0 while the runtime-masked IIO >>32 yields the unmasked value β the GPU-register values the interpreter computes for out-of-range counts are compiler-dependent. No memory-safety consequence; Low/hardening. Kernel runtime hardware-gated (no ATI GPU on guest).
No comments yet.