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

sq_edc_source_names[] indexed by unbounded 3-bit SQ_EDC_INFO.SOURCE field: OOB rodata pointer handed to %s

Summary

SQ_EDC_INFO.SOURCE is a 3-bit field (0..7) but sq_edc_source_names[] has 7 entries; the DRM_INFO uses sq_edc_source_names[sq_edc_source] guarded only by != -1. Reserved encoding 7 reads the rodata pointer after the array and %s walks it: fault inside the printk formatter during SQ interrupt handling (panic in an error-reporting path) or printing of adjacent static rodata to dmesg. Hardware EDC event latching the reserved source encoding; not directly commandable by unprivileged GPU users - robustness of the error path with panic side effect and minor static-content disclosure to dmesg.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3600 Β· 7 files
FileTypeDescriptionSize
run.sh β€” 89 B view raw
run.log β€” 1003 B view raw
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
fix.diff β€” 575 B view raw
manifest.json β€” 863 B view raw
verdict.json β€” 2.0 KB view raw

DF-3600 β€” sq_edc_source_names[] indexed by unbounded 3-bit HW field (OOB rodata read -> %s)

What

gfx_v8_0_parse_sq_irq() reads the EDC source from hardware:

    gfx_v8_0.c:6811
    sq_edc_source = REG_GET_FIELD(RREG32(mmSQ_EDC_INFO), SQ_EDC_INFO, SOURCE);

SQ_EDC_INFO__SOURCE_MASK = 0x1c0 (3-bit field, sys/dev/drm/amd/include/asic_reg/gca/gfx_8_0_sh_mask.h:14083) β†’ values 0..7, but the lookup table has only 7 entries:

    gfx_v8_0.c:710-718
    static const char * const sq_edc_source_names[] = {
        "SQ_EDC_INFO_SOURCE_INVALID", ... "SQ_EDC_INFO_SOURCE_TA",   /* 7 entries */
    };

and the printf at :6831 does sq_edc_source_names[sq_edc_source] with no bound check. When the field reads 7 (reserved encoding β€” real on parts where the EDC source latches garbage, e.g. uncorrected-but-unmapped EDC events), index 7 reads one pointer past the array in kernel rodata and %s walks it:

  • if the adjacent qword is not a valid string pointer β†’ kernel page fault from the log formatter while handling an SQ interrupt (panic from what is supposed to be an error-reporting path), or
  • if it happens to be a valid pointer, it prints unrelated kernel rodata to dmesg (minor info disclosure of static kernel content).

Path: gfx_v8_0_sq_irq :6848-6867 β†’ gfx_v8_0_parse_sq_irq :6769 (both ISR and work-queue contexts; in_task() only gates whether the SQ_EDC_INFO register is read β€” in ISR context sq_edc_source stays -1 and is safe; the unbounded index is the work-queue/task path).

Trigger / severity

Requires the GPU to raise an SQ interrupt with EDC/ECC encoding whose SQ_EDC_INFO.SOURCE latches 7 β€” a hardware error-event path (not directly commandable by unprivileged GPU users), so: Low, defense-in-depth with a panic side effect.

Reproduce

Source assertion (guest is virtio-only, no AMD HW):

./run.sh

prints the 7-entry array, the 3-bit mask, and the unbounded index site.

Fix

fix.diff: bound the index with ARRAY_SIZE(sq_edc_source_names).

VERDICT.md
↓ download raw

DF-3600 β€” VERDICT

status: inconclusive (kernel run) / source certain reproduced: 0

Proof

  • sq_edc_source_names[] (gfx_v8_0.c:710-718) has exactly 7 elements (INVALID, INST, SGPR, VGPR, LDS, GDS, TA).
  • SQ_EDC_INFO__SOURCE_MASK = 0x1c0, __SHIFT = 0x6 (gfx_8_0_sh_mask.h:14083-14084) β†’ a 3-bit field, representable range 0..7. Encoding 7 is reserved/invalid on the hardware side.
  • gfx_v8_0_parse_sq_irq :6811 stores the field via REG_GET_FIELD, and the DRM_INFO at :6831 evaluates (sq_edc_source != -1) ? sq_edc_source_names[sq_edc_source] : "unavailable". The only guard is != -1 (the ISR-context sentinel); nothing bounds the value to the array. Source == 7 β†’ sq_edc_source_names[7] reads the rodata qword after the array and hands it to %s:
  • invalid pointer β†’ fault inside the printk/%s formatter while servicing an SQ interrupt (kernel panic in the error handler), or
  • valid pointer β†’ adjacent kernel rodata printed to dmesg (minor static- content disclosure).

  • Trigger: SQ interrupt with enc 1/2 processed in task context (gfx_v8_0_sq_irq :6848-6867 β†’ work func :6839-6846) while SQ_EDC_INFO.SOURCE latches the reserved encoding 7 β€” a hardware EDC event path, not directly controllable by unprivileged GPU users. Severity Low (robustness of an error-reporting path).

Why not a kernel run

Audit guest is virtio-only (no AMD GPU, no drm/amdgpu module, /boot/modules absent). inconclusive at runtime; index/mask mismatch proven from source (run.log).

Fix validation

fix.diff bounds the index with ARRAY_SIZE(sq_edc_source_names). Not buildable on this guest β€” fix_status: not_testable; diff line-matched to gfx_v8_0.c:6831.

kernel_refs

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

fix.diff adds the ARRAY_SIZE bound at :6831; not buildable/testable on the virtio-only guest (amdgpu module absent).

findings/poc/DF-3600/fix.diff
↓ fix.diffper-fix-DF-3600

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (7 entries vs 3-bit mask vs unguarded index); VERDICT.md; fix.diff

PoC changes

authored fresh (pass 2)

Verified recommended fix

Bound the index: use sq_edc_source only when 0 <= sq_edc_source < ARRAY_SIZE(sq_edc_source_names); see fix.diff.

Verdict

gfx_v8_0_parse_sq_irq reads the 3-bit SQ_EDC_INFO.SOURCE field (mask 0x1c0, gfx_8_0_sh_mask.h:14083 => 0..7) at gfx_v8_0.c:6811 and prints sq_edc_source_names[sq_edc_source] at :6831 with only a != -1 guard; the array (gfx_v8_0.c:710-718) has 7 entries, so reserved encoding 7 indexes one pointer past the array in kernel rodata and hands it to %s - fault inside the printk formatter during SQ interrupt handling (panic in an error-reporting path) or printing of adjacent static rodata to dmesg. HW-error-event triggered, not directly attacker-commandable -> Low. Not executable on the virtio-only audit guest -> inconclusive at runtime; mismatch proven from source.