DF-4238 / verdict.json
{ "finding_id": "DF-4238", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Parsing primitive proven by a guest-run harness; the kernel path is hardware-gated (no AMD GPU on the audit guest - pciconf shows zero 0x1002 devices, env.txt). sdma_v3_0_init_microcode() validates each SDMA firmware image only via amdgpu_ucode_validate() (amdgpu_ucode.c:251-260, single equality fw->datasize == hdr->size_bytes) and then reads beyond the common header: hdr->header.ucode_version at sdma_v3_0.c:316 (offset +16), hdr->ucode_feature_version at :317 (offset +32; sdma_firmware_header_v1_0 is 48 bytes), and header->ucode_size_bytes at :324-326 (offset +24) folded into adev->firmware.fw_size which later sizes the firmware BO memcpyd at amdgpu_ucode.c:347-351 (DF-1947 sink; VI load type is AMDGPU_FW_LOAD_SMU per amdgpu_ucode.c:291-300). A 4-byte blob declaring size_bytes=4 passes validation; harness (fwcheck.c, guard-page stand-in for the exact-size request_firmware allocation) shows validate returns 0 and the very first sub-header read faults with SIGSEGV exit 139 - the exact access order of the kernel function. Root-installed firmware gate, same threat model/severity as family findings DF-1451 (sdma_v4_0), DF-1553 (cik_sdma), DF-1598 (sdma_v2_4).", "exploit_chain": "root replaces /boot amdgpufw_<asic>_sdma[_1] with a 4-byte file whose first u32 is 4 -> amdgpu_ucode_validate passes -> sdma_v3_0.c:316/:317/:326 read 16/24/32 bytes past the allocation -> heap OOB read: garbage fw_version/feature_version (dmesg leak, burst_nop misconfig) and attacker-influenced OOB-read value inflates fw_size feeding the DF-1947 unbounded memcpy of ucode_size_bytes from fw->data+ucode_array_offset_bytes.", "evidence": "run.log: amdgpu_ucode_validate(4-byte blob) = 0 (0 == accepted) then RUN_EXIT=139 (SIGSEGV) at the +16 read; VERDICT.md has the full line-cited trace; fix.diff applies cleanly to the unmodified sys file (patch --dry-run verified).", "kernel_refs": [ "sys/dev/drm/amd/amdgpu/sdma_v3_0.c:309-317", "sys/dev/drm/amd/amdgpu/sdma_v3_0.c:321-326", "sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260", "sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:347-351", "sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:120-126" ], "poc_changes": "No seed PoC; fwcheck.c is a guard-page harness (no ASan needed on the DragonFly guest) replicating the validate-then-read sequence with struct layouts mirrored from amdgpu_ucode.h.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o fwcheck fwcheck.c", "run_cmd": "./fwcheck; echo $? (139 = SIGSEGV)", "code_hash": "a6b75b1559f8c807a21a987d3ecca843807d03aa11c92b0f353194229d541b24", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "not_testable (no AMD hardware on the guest); fix.diff rejects datasize < sizeof(struct sdma_firmware_header_v1_0) before the sub-header reads - the v3 twin of the DF-1598 fix. Full header cross-validation belongs in amdgpu_ucode_validate (DF-1946); memcpy sink hardening is DF-1947.", "fix_evidence": "fix.diff (patch --dry-run clean against unmodified sys/dev/drm/amd/amdgpu/sdma_v3_0.c); harness output demonstrates the accepted-then-rejected sequence the guard implements.", "notes": "Hardware-gated kernel path, primitive guest-proven. Per-file twin of DF-1451/DF-1553/DF-1598; shared-root findings DF-1946/DF-1947 remain the umbrella. sdma_v3_0.c otherwise audited clean: emit accounting (emit_frame_size 6+3+6+VI_FLUSH_GPU_TLB_NUM_WREG*3+6+30 vs actual writes 6/3/6/12/max-10; vm_flush over-reserved), interrupt dispatcher switch-cases ring_id to fixed instances, instance walks bounded by SDMA_MAX_INSTANCE=2, suspend/resume symmetric, doorbell indices constants.", "recommended_fix": "In sdma_v3_0_init_microcode, reject firmware with fw->datasize < sizeof(struct sdma_firmware_header_v1_0) (and ideally ucode_array_offset_bytes+ucode_size_bytes > datasize) before reading any sub-header field." } |