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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4340 Β· 8 files
FileTypeDescriptionSize
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; β€” shift is 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): shift is a VBIOS byte (0..255);
  • WS masks (atom.c:252/255): 1 << gctx->shift with gctx->shift a 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.)

VERDICT.md
↓ download raw

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 to 0x00000000 while (0xffff << 8) gives 0x00ffff00 β€” 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

inconclusive
baseline no→ patch + rebuild →patched clean

fix.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)
↓ fix.diffper-fix-DF-4340

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).