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

VBIOS retimer HdmiRegNum/Hdmi6GRegNum (u8, unclamped) drive heap OOB write past kzalloc'd struct integrated_info in get_integrated_info_v11

Summary

The four dpN_retimer_set blocks are copied with EIGHT loops bounded directly by the raw u8 HdmiRegNum/Hdmi6GRegNum fields into fixed [9]/[3] destination arrays. A crafted count of 255 writes 510 sequential, fully attacker-controlled byte-pairs starting ~110 bytes into a 692-byte kzalloc'd object - up to ~500 bytes past the allocation, ~170 bytes into the adjacent kmalloc-1024 slab object. The same loops also read past the [9]/[3] source arrays. Crafted/corrupted VBIOS on a DCE12+ ASIC (flashed ROM, vfio passthrough romfile, malicious peripheral firmware): driver-load-time kernel heap overflow with attacker-chosen content and length. 8-clamps fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4165 Β· 20 files
FileTypeDescriptionSize
README.md β€” 4.3 KB ↓ raw
VERDICT.md β€” 7.4 KB ↓ raw
rig/gen_rig.sh β€” 5.3 KB view raw
rig/check_verbatim.sh β€” 2.7 KB view raw
rig/bios_slice_verbatim.h β€” 17.2 KB view raw
rig/bios_slice_verbatim.c β€” 14.0 KB view raw
rig/rig_shims.h β€” 2.8 KB view raw
rig/retimer_rig.c β€” 14.8 KB view raw
rig/Makefile β€” 439 B ↓ download
rig/build.sh β€” 393 B view raw
rig/run.sh β€” 620 B view raw
build.log β€” 2.2 KB view raw
run.log β€” 9.4 KB view raw
run.2.log β€” 9.4 KB view raw
fix_validation.log β€” 3.0 KB view raw
code_hashes.txt β€” 258 B view raw
env.txt β€” 681 B view raw
fix.diff β€” 3.4 KB view raw
verdict.json β€” 6.5 KB view raw
manifest.json β€” 1.9 KB view raw

DF-4165 β€” VBIOS retimer HdmiRegNum/Hdmi6GRegNum drive OOB register-index copy loops past fixed [9]/[3] arrays in struct integrated_info

  • File: sys/dev/drm/amd/display/dc/bios/bios_parser2.c β€” get_integrated_info_v11() (:1367-1578), reached from bios_parser2_construct (:1989) β†’ bios_parser_create_integrated_info (:1645, kzalloc(sizeof(struct integrated_info)) at :1651) β†’ construct_integrated_info (:1595, dispatches on ATOM table content_revision == 11)
  • Class: kernel heap buffer overflow (write + read), CWE-787 / CWE-125
  • Verified via the verbatim-code rig (rig/): the production parser functions and every struct they touch were sed-extracted byte-identical from sys/ (gen_rig.sh), compiled on the DragonFly guest, and driven with a crafted ATOM integratedsysteminfo v1_11 image whose retimer count bytes exceed the array bounds. check_verbatim.sh re-proved block identity against the guest's own /usr/src/sys (the tree its running kernel was built from).
  • Hardware gating: the guest has only the Bochs SVGA (0x1234:1111), no dev.drm, no amdgpu (see env.txt) β€” the parser never executes in this guest's kernel. In-kernel execution requires an AMD DCE12-class ASIC (bios_parser2 path, e.g. Vega10/Raven) presenting the crafted table, i.e. a crafted/corrupted VBIOS (flashed ROM, vfio-pci romfile=, malicious device). That is exactly the threat model of the finding.

Layout (printed by the rig from the verbatim structs; matches the kernel ABI)

sizeof(struct integrated_info)                      = 692   (kzalloc'd at bios_parser2.c:1651)
  dp0_ext_hdmi_reg_settings[9]     @ 582   dp0_ext_hdmi_6g_reg_settings[3] @ 601
  dp1_ext_hdmi_reg_settings[9]     @ 609   dp1_ext_hdmi_6g_reg_settings[3] @ 628
  dp2_ext_hdmi_reg_settings[9]     @ 636   dp2_ext_hdmi_6g_reg_settings[3] @ 655
  dp3_ext_hdmi_reg_settings[9]     @ 663   dp3_ext_hdmi_6g_reg_settings[3] @ 682
  dp_ss_control (last member)      @ 688
sizeof(atom_integrated_system_info_v1_11)           = 1140
sizeof(atom_hdmi_retimer_redriver_set)              = 27    (HdmiRegSetting@3 [9x2B], Hdmi6GhzRegSetting@21 [3x2B])

With Hdmi6GRegNum = 255 (u8) on dp3, the loop writes bytes 682..1191 of the 692-byte kzalloc'd object: 500 bytes past its end (in-kernel: past the kmalloc-1024 bucket slot at offset 1024 β†’ ~168 bytes into the adjacent slab object). Count fields are raw u8 image bytes; there is no clamp anywhere on the path.

Build (on the DragonFly guest)

cd rig && sh ./build.sh /usr/src     # verbatim check + cc both binaries

Builds rig_base (production code, byte-identical) and rig_fixed (same slice + the 16 clamp lines of ../fix.diff under -DAPPLY_DF4165_FIX; the checker verifies the guarded lines are exactly fix.diff's additions).

Run

cd rig && sh ./run.sh

Five modes per build: layout, dest-guard (kzalloc object ends at a PROT_NONE page), src-guard (v1_11 table ends at the mapped BIOS-image end), canary (only dp0.HdmiRegNum=255, adjacent victim pre-filled 0x41), canary6g (only dp3.Hdmi6GRegNum=255).

Expected output (see run.log / run.2.log)

Baseline (rig_base): - dest-guard: SIGSEGV, fault offset ... 692 (sizeof=692) -> 0 bytes PAST the allocation end => OUT-OF-BOUNDS WRITE (rc=88) - src-guard: SIGSEGV, fault offset in BIOS image: 4096 (bios_size=4096) => READ BEYOND BIOS IMAGE (OOB READ) (rc=88) - canary: VICTIM bytes changed: 400/512 ... 400 match / 0 mismatch β€” every byte written past the 692-byte object is byte-for-byte the crafted image byte from the corresponding source position - canary6g: 500/512 ... 500 match / 0 mismatch, dp_ss_control (last member) clobbered with crafted bytes

Fixed (rig_fixed): both guard modes complete rc=0 with stored counts 9/3, canary modes show VICTIM bytes changed: 0/512, victim intact.

Verdict

Reproduced (verbatim-rig evidence class): the exact production parser code, byte-identical to the running kernel's source, performs a fully attacker-controlled linear heap overflow of up to 500 bytes past the destination object plus OOB reads past the source ATOM table, on a crafted VBIOS. fix.diff (clamp all eight counts to 9/3) eliminates it β€” A/B validated in the same rig on the identical crafted image. See VERDICT.md, verdict.json, fix_validation.log.

VERDICT.md
↓ download raw

DF-4165 β€” VERDICT (verify run, 2026-10-14)

Status: REPRODUCED (verbatim-rig evidence class). Memory corruption confirmed in byte-identical production code; in-kernel execution is AMD-ASIC-gated on this guest (no AMD GPU β€” proven, see env.txt). Fix A/B validated in the same rig: fixed.

1. What was verified and how

The guest cannot run the amdgpu/dc driver (Bochs SVGA 0x1234:1111 only, sysctl dev.drm unknown, no drm/amdgpu KLDs β€” env.txt). Per the established evidence class for crafted-firmware parsers, the actual production parser was compiled into a guest-side harness:

  • rig/gen_rig.sh sed-extracts the code byte-identical from the read-only sys/ tree: get_integrated_info_v11 (bios_parser2.c:1367-1578), construct_integrated_info (:1595-1643, revision dispatch), bios_parser_create_integrated_info (:1645-1664, the kzalloc), get_atom_data_table_revision (:133-151), bios_get_image (bios_parser_helper.c:36-44, the only bounds check on the path), plus every struct they touch (atomfirmware.h, grph_object_ctrl_defs.h, grph_object_id.h, bios_parser_types.h, bios_parser_types_internal2.h) and the exact DATA_TABLES/GET_IMAGE macros (bios_parser2.c:110, bios_parser_helper.h:39).
  • rig/check_verbatim.sh re-proved all 21 blocks byte-identical β€” run both against the host sys/ tree and on the guest against /usr/src/sys, the tree its running kernel (Jul 2 2026 build) was compiled from: ALL VERBATIM CHECKS PASSED (build.log). The only inserted lines anywhere are the 16 #ifdef APPLY_DF4165_FIX clamps, verified identical to fix.diff's additions.
  • Harness shims (rig_shims.h) touch nothing on the overflow path: minimized dc_bios/bios_parser (same member names, same first-member layout so BP_FROM_DCB container_of semantics hold), kzallocβ†’placement, LE-host le*_to_cpu identity, and a shim for object_id_from_bios_object_id (feeds only device_connector_id, never the retimer arrays).

The crafted image mimics a real ATOM ROM: master data table v2_1 at offset 0x20 with integratedsysteminfo pointing at a v1_11 table whose content_revision = 11 drives the production dispatch (construct_integrated_info switch (revision.minor) case 11: get_integrated_info_v11). Count bytes set per mode; the whole image is attacker pattern data (a crafted ROM controls every byte).

2. Observed (run.log, stable across run.2.log)

Exact layout from the verbatim structs: sizeof(struct integrated_info)=692, dest arrays at 582/601, 609/628, 636/655, 663/682, last member dp_ss_control@688; source atom_integrated_system_info_v1_11=1140 B, atom_hdmi_retimer_redriver_set=27 B (HdmiRegSetting@3 [9Γ—2], Hdmi6GhzRegSetting@21 [3Γ—2]).

  • dest-guard (kzalloc object ends exactly at a PROT_NONE page; all eight counts = 255): SIGSEGV ... fault offset ... 692 (sizeof=692) -> 0 bytes PAST the allocation end => OUT-OF-BOUNDS WRITE (rc=88). The first faulting write is the first byte past the 692-byte allocation β€” the production write loop walks straight off the object.
  • src-guard (table ends at the mapped image end; destination kept in a huge writable mapping so only a source read can fault; only dp3.Hdmi6GRegNum=255): SIGSEGV ... fault offset in BIOS image: 4096 (bios_size=4096) => READ BEYOND BIOS IMAGE (OOB READ) (rc=88). The loop indexed Hdmi6GhzRegSetting[i] past the [3]-entry array, past the whole 1140-B table, and past the entire BIOS image.
  • canary (dp0.HdmiRegNum=255 only; adjacent 512-B victim pre-filled 0x41): 400/512 victim bytes changed, first changed byte at victim+0 = struct+692 = the first byte past the object, and 400/400 changed bytes equal the crafted-image byte at the exact source position (0 mismatch). dp_ss_control overwritten with crafted bytes.
  • canary6g (dp3.Hdmi6GRegNum=255 only): 500/512 victim bytes changed (write window 682..1191 β†’ 500 bytes past the 692-B object), 500/500 byte-exact to the crafted image. In-kernel this kzalloc(692) lands in a kmalloc-1024 bucket: writes 1024..1191 (~168 B) reach the adjacent slab object; bytes 692..1023 corrupt the remainder of this slot. The copy is linear, sequential, no gaps, fully value- and length-controlled by image bytes (any count 10..255 β†’ 2 to 500 bytes past the object).

3. Primitive and impact

Primitive: linear, fully attacker-controlled kernel-heap overflow of up to 500 bytes past a kzalloc'd 692-B struct integrated_info at driver init-time (bios_parser2.c:1651 β†’ :1989), plus OOB reads past the source ATOM table (up to ~470 B past the 27-B retimer set; with a realistically sized image the source reads stay inside the ROM buffer, so every written byte is attacker-chosen). Gating: requires an AMD ASIC whose VBIOS carries integratedsysteminfo v1_11 with out-of-bounds count bytes β€” i.e. crafted / corrupted GPU firmware (flashed ROM, vfio-pci romfile override, malicious device). No unprivileged userland trigger exists on stock hardware: the counts come from the GPU ROM image, copied to kernel memory at driver attach.

In-kernel ceiling: adjacent-object corruption at module/driver initialization β†’ allocator panic (DoS floor) with potential for more given the fully controlled content; under the finding's hardware/firmware attacker model this is a code-execution-class primitive. Demonstrated on this guest out-of-kernel-context (rig); impact recorded as dos (crash-class floor), not panic (the guest kernel never executed the path).

4. Fix validation (A/B, identical crafted image)

rig_fixed = same verbatim slice + exactly the 16 clamp lines of fix.diff (checker-verified equal to fix.diff additions) under -DAPPLY_DF4165_FIX:

  • dest-guard / src-guard: no fault, rc=0, all four retimer blocks clamped (dp0=9/3 dp1=9/3 dp2=9/3 dp3=9/3);
  • canary / canary6g: 0/512 victim bytes changed, victim intact, counts stored as 9 and 3.

Baseline corrupts, patched slice is clean on the same input β†’ fix validated (fix_validation.log). A rebuilt-kernel validation was 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 A/B-validated in the same rig). fix.diff remains authored against the pristine read-only sys/ tree and was never applied there.

5. Honest limitations

  • The overflow is demonstrated with production instructions and production struct layouts, but in a userland mapping, not the kernel's kmalloc-1024 slab. The allocator-adjacency arithmetic (slot end 1024, ~168 B into the neighbor) is derived from DragonFly kmalloc bucketing, not measured in kernel.
  • The crafted image is synthetic (master table + v1_11 table only); a real crafted ROM must also satisfy earlier parser stages (ROM header, object tables) before bios_parser_create_integrated_info runs β€” those stages precede and do not constrain the retimer counts.
  • bios_get_image's offset + size < bios_size check bounds only the START of the v1_11 table (verified in-rig: a table at image end passes the check and still faults mid-loop), matching production behavior.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/B validated in the verbatim rig: identical crafted image, same slice, only the 16 clamp lines of fix.diff added (-DAPPLY_DF4165_FIX). Baseline: OOB-write fault at struct+692, OOB-read fault past the image, 400+500 victim bytes corrupted byte-exact. Fixed: every mode completes rc=0, no fault, stored counts clamped to 9 and 3, victim intact (0/512 changed). Kernel-rebuild validation not performed: the fix site executes in-kernel only on AMD ASICs, absent from this guest (no AMD GPU per env.txt); 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_validation.log (baseline vs fixed A/B extract); run.log FIXED sections; build.log (fix lines == fix.diff additions); fix.diff
↓ fix.diffper-fix-DF-4165

Confirmed kernel references

Detail

Exploit chain

Hardware/firmware attacker model only: craft VBIOS with integratedsysteminfo v1_11 (content_revision=11) and any dpN_retimer_set.HdmiRegNum>9 or Hdmi6GRegNum>3 -> amdgpu attach -> dc construct -> bios_parser2_construct (bios_parser2.c:1989) -> bios_parser_create_integrated_info kzalloc(692) (:1651) -> revision dispatch (:1615) -> get_integrated_info_v11 unclamped loops write 2*count bytes starting at dpN_ext_hdmi_reg_settings (offset 582/609/636/663) or dpN_ext_hdmi_6g_reg_settings (601/628/655/682) -> sequential attacker-value-controlled heap overflow up to 500 bytes past the object into the kmalloc-1024 neighbor (kernel impact floor: slab corruption/panic at driver init; content fully controlled). No unprivileged-user chain exists - counts come from GPU ROM, not syscalls.

Evidence (decisive lines)

run.log (all five modes, baseline vs fixed), run.2.log (stability), fix_validation.log (A/B extract), build.log (22x 'identical to production' verbatim checks incl. guest /usr/src/sys + zero-warning build of both binaries), VERDICT.md (full narrative + limitations), rig/ (sources), env.txt (hardware gating), code_hashes.txt

PoC changes

Seed pack had no runnable PoC (status untested, hardware-gated). Built the verbatim rig from scratch: gen_rig.sh extracts the production slice byte-identical; check_verbatim.sh proves block identity (host tree AND guest /usr/src/sys) and that the APPLY_DF4165_FIX lines are exactly fix.diff's additions; harness drives five modes with guard pages and a 0x41 canary victim; the crafted image includes the master-data-table indirection and the content_revision=11 dispatch so the production dispatch chain executes.

Verified recommended fix

Clamp all eight retimer register counts to their destination array sizes (9 / 3) before the copy loops - fix.diff, A/B validated in the rig.

Verdict

REPRODUCED via the verbatim-code rig: get_integrated_info_v11() (bios_parser2.c:1367-1578), its dispatch construct_integrated_info() (:1595) and the kzalloc(692) at :1651 were sed-extracted byte-identical from sys/ (21 blocks re-verified identical ON THE GUEST against /usr/src/sys, the tree its running kernel was built from) and driven with a crafted ATOM integratedsysteminfo v1_11 image (content_revision=11) whose retimer count bytes exceed the arrays. Baseline production code: dest-guard mode SIGSEGVs writing at exactly struct+692 = the first byte past the kzalloc'd 692-byte struct integrated_info (OUT-OF-BOUNDS WRITE); src-guard mode SIGSEGVs reading image offset 4096 with bios_size=4096 (Hdmi6GhzRegSetting[i] indexed past the [3] array, past the 1140-byte table, past the whole BIOS image - OOB READ); canary modes show a linear, fully attacker-controlled overwrite of 400 (dp0.HdmiRegNum=255) and 500 (dp3.Hdmi6GRegNum=255) bytes past the object, every changed byte byte-exact equal to the crafted-image byte at the corresponding source position (900/900, zero mismatches), including clobber of the last struct member dp_ss_control@688. In-kernel: kzalloc(692) lands in a kmalloc-1024 slot, so the 500-byte overflow runs ~168 bytes into the adjacent slab object. Gating: requires an AMD DCE12-class ASIC with crafted/corrupted VBIOS (flashed ROM, vfio romfile, malicious device) - the count fields are raw u8 image bytes from the GPU ROM copied to kernel memory at driver attach; there is NO unprivileged userland trigger, and this guest has no AMD GPU (Bochs 0x1234:1111 only, no dev.drm, no amdgpu - env.txt), so the guest kernel never executes the path; reproduction is in the verbatim rig per the established evidence class for crafted-firmware parsers. fix.diff (clamp all eight counts to 9/3) A/B-validated in the same rig on the identical image: no faults, counts clamped 9/3, victim intact 0/512 changed.