⬢ DragonFlyBSD Kernel Audit
DF-4168 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4168",
  "status": "reproduced",
  "reproduced": true,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "REPRODUCED via the verbatim-code rig: bios_parser_construct (bios_parser2.c:1912-1992, the bios_size = bios[2]*512 computation at :1930), firmware_parser_create (:1994-2009), bios_get_image (bios_parser_helper.c:36-44, the only bounds check: offset+size < bp->bios_size), and the firmware-info walk (bios_parser_get_firmware_info :1119-1155 -> get_firmware_info_v3_2 :1204-1280) were sed-extracted byte-identical from sys/ (30 blocks re-verified identical ON THE GUEST against /usr/src/sys, the tree its running kernel was built from; the checker also proves the APPLY_DF4168_FIX construct equals the bios_parser2.c hunk of fix.diff applied to production, and the bp_init_data member insert equals the bios_parser_interface.h hunk). Crafted image: a fully valid ATOM ROM (header v2.2 @0x40, master data table v2_1 @0x20, displayobjectinfo v1_4 @0x60 -- everything construct needs, all INSIDE the 4096-byte allocation) with bios[2]=0xFF claiming 130,560 bytes. Baseline production code: (a) size-mismatch -- construct SUCCEEDS with bp->base.bios_size=130560 against a 4096-byte allocation (31x over-trust; read ceiling ~126KB past this allocation, the findings ~95KB figure = 32KB-alloc case); (b) oob-guard -- firmwareinfo @0x1100 / dce_info @0x1300 (past the allocation, admitted by the claimed bound) -> SIGSEGV READING at mapping offset 4354 = 0x1102, 258 bytes PAST the allocation, in the production walk (rc=88); (c) oob-canary -- adjacent region readable, tables crafted there -> BP_RESULT_OK with crystal_frequency = 46600 = 0x1234 x 10 read from offset 0x1300, 768 bytes past the allocation: adjacent-heap bytes parsed into DC state (plain 0x41 filler parsed as 0x4141 -> 167050 in an earlier run); (d) guard-construct -- image[0x48]=0x2500 -> constructs OWN GET_IMAGE(atom_rom_header_v2_2, ...) walk (bios_parser2.c:1942) SIGSEGVs reading at offset 9474 past the allocation. Allocation-side facts confirmed in source: amdgpu_read_bios kzallocs the pci_map_rom size (amdgpu_bios.c:130-141), check_atom_bios (:50-81) never cross-checks bios[2]*512 against it, and struct bp_init_data (bios_parser_interface.h:33-36) carries only the pointer -- the allocation length is never plumbed into DC, exactly the gap fix.diff closes (5 files: dc_types.h, amdgpu_dm.c, bios_parser_interface.h, dc.c, bios_parser2.c clamp). Gating: AMD ASIC + crafted/oversized-claim ROM (small ROM BAR, vfio/QEMU romfile sizing, malicious device); no unprivileged userland trigger; guest has no AMD GPU (Bochs 0x1234:1111, no dev.drm, no amdgpu - env.txt), so the guest kernel never executes the path. fix.diff A/B-validated in the same rig on the identical image: bios_size clamped to 4096; out-of-allocation tables rejected (BADBIOSTABLE, sentinel info struct untouched, no adjacent-region reads); constructs own rom-header walk rejected cleanly; zero faults.",
  "exploit_chain": "Hardware/firmware attacker model only: craft VBIOS image whose bios[2] size byte exceeds the drivers true allocation (small ROM BAR device, vfio-pci romfile=, malicious PCI device) while carrying valid-format tables -> amdgpu attach -> adev->bios = kzalloc(true_size) -> DC bios_parser_construct trusts bios[2]*512 as bios_size -> every bios_get_image walk (constructs own rom-header read at :1942 and all table walks) may read past the kzallocd buffer, up to ~126KB with bios[2]=0xFF -> OOB kernel heap reads: crash when the past-end pages are unmapped (demonstrated guard-page SIGSEGVs) and silent consumption of adjacent slab contents as parsed DC state (demonstrated canary: adjacent bytes became the display reference clock). No write primitive; no unprivileged userland trigger (values originate in the GPU ROM).",
  "evidence": "run.log / run.2.log (all five modes baseline vs fixed, stable across two runs), fix_validation.log (full A/B), build.log (30x identical to production incl. guest /usr/src/sys + fix-variant==patch-application proofs + zero-warning builds), VERDICT.md (full narrative incl. allocation-side source citations and limitations), rig/ (sources), env.txt (hardware gating), code_hashes.txt",
  "kernel_refs": [
    "sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1930",
    "sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1942",
    "sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:40",
    "sys/dev/drm/amd/display/include/bios_parser_interface.h:33",
    "sys/dev/drm/amd/amdgpu/amdgpu_bios.c:60",
    "sys/dev/drm/amd/amdgpu/amdgpu_bios.c:135"
  ],
  "poc_changes": "Seed pack had no runnable PoC (status untested, hardware-gated). Built the verbatim rig from scratch: gen_rig.sh extracts construct + create + the walk + bios_get_image and every struct byte-identical; check_verbatim.sh proves block identity (host tree AND guest /usr/src/sys) and reconstructs the fixed construct by applying the bios_parser2.c hunk of fix.diff to the production text (proving the APPLY_DF4168_FIX variant equals the patch), plus the bp_init_data member == interface.h additions; the harness plays the role of fix.diffs amdgpu_dm.c/dc.c plumbing by filling init.bios_size with the true allocation. Harness bugs fixed during the run: masterdatatable_offset must be the ABSOLUTE image offset (a relative value wrapped u16 and faulted inside construct before the intended scenario), field offsets now computed via offsetof on the verbatim structs, fflush before child _exit so fixed-build child output is captured.",
  "attempts": 3,
  "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": 1500,
  "guest_dirty": 0,
  "build_cmd": "cd /home/maxx/DF-4168/rig && sh ./build.sh /usr/src",
  "run_cmd": "cd /home/maxx/DF-4168/rig && sh ./run.sh",
  "code_hash": "size_slice_verbatim.c 9db774c698b5eaec8d5ec1332d217f322e053b1bac4f2b238fcfcd71ea3ffe3a; size_slice_verbatim.h f8a1d1b0b777e19958a43efd6db63219c0afefdcd5ffd2334e19e7ff6b48fde8; size_rig.c 1cf231d1809be6ab27e2b95763f8828b1f68682e2f959af8464b82d73d3185cf (full list in code_hashes.txt)",
  "notes": "Impact leak = OOB heap READ class (CWE-125): adjacent kernel heap contents are consumed as parser data (canary: adjacent bytes became the display reference clock); the crash flavor is also demonstrated (guard-page SIGSEGVs at offsets the claimed bound admits) but reads are the primary primitive and no write arises from this finding (the write side of this parser is DF-4165). Direct copy-to-userspace of the OOB bytes was NOT demonstrated -- exfiltration lands in DC-internal parsed state, hence leak rather than a demonstrated userland disclosure. Deterministic across run.log and run.2.log. The same claimed-size bound governs every other table walk in the file (object tables, encoder caps, board layout, integratedsysteminfo); each was not separately re-driven. Guest left up and clean (only /home/maxx/DF-4168 scratch; no kernel or system state changed).",
  "recommended_fix": "Plumb the true allocated VBIOS length into DC (adev->bios_size via amdgpu_dm/dc into bp_init_data.bios_size) and clamp bios_size = min(bios[2]*512, bios_size) in bios_parser_construct - fix.diff (5 files), A/B validated in the rig.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "A/B validated in the verbatim rig: identical crafted image, same slice, only the bios_parser2.c clamp + bp_init_data.bios_size member of fix.diff under -DAPPLY_DF4168_FIX (checker-proven equal to the patch applied to production; the harness fills init.bios_size, the role of the dc.c/amdgpu_dm.c plumbing hunks). Baseline: construct SUCCEEDS claiming 130560 against a 4096 allocation; SIGSEGV reads past the allocation in the firmware-info walk and in constructs own rom-header walk; adjacent-region bytes parsed into DC state. Fixed: bios_size clamped to 4096; out-of-allocation tables rejected (BP_RESULT_BADBIOSTABLE, sentinel untouched, no adjacent reads); constructs rom-header walk rejected cleanly; zero faults. Kernel-rebuild validation not performed: the fix site executes in-kernel only on AMD ASICs absent from this guest; per the verbatim-rig protocol the fixed slice was validated in the same rig. fix.diff authored against the pristine read-only sys/ tree, never applied there.",
  "fix_evidence": "fix_validation.log (full baseline vs fixed output); run.log FIXED sections; build.log (APPLY_DF4168_FIX construct == fix.diff applied to bios_parser2.c:1912-1992; bp_init_data member == bios_parser_interface.h additions); fix.diff"
}