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)
PoC verification
Evidence pack
findings/poc/DF-4165 Β· 20 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 frombios_parser2_construct(:1989) βbios_parser_create_integrated_info(:1645,kzalloc(sizeof(struct integrated_info))at :1651) βconstruct_integrated_info(:1595, dispatches on ATOM tablecontent_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 fromsys/(gen_rig.sh), compiled on the DragonFly guest, and driven with a crafted ATOMintegratedsysteminfo v1_11image whose retimer count bytes exceed the array bounds.check_verbatim.shre-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 (seeenv.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-pciromfile=, 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.
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.shsed-extracts the code byte-identical from the read-onlysys/tree:get_integrated_info_v11(bios_parser2.c:1367-1578),construct_integrated_info(:1595-1643, revision dispatch),bios_parser_create_integrated_info(:1645-1664, thekzalloc),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 exactDATA_TABLES/GET_IMAGEmacros (bios_parser2.c:110, bios_parser_helper.h:39).rig/check_verbatim.shre-proved all 21 blocks byte-identical β run both against the hostsys/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_FIXclamps, verified identical tofix.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 soBP_FROM_DCBcontainer_of semantics hold), kzallocβplacement, LE-hostle*_to_cpuidentity, and a shim forobject_id_from_bios_object_id(feeds onlydevice_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 indexedHdmi6GhzRegSetting[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_controloverwritten 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_inforuns β those stages precede and do not constrain the retimer counts. bios_get_image'soffset + size < bios_sizecheck 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
fixedA/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
Confirmed kernel references
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1441
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1448
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1489
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1496
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1615
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1651
- sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1989
- sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:38
- sys/dev/drm/amd/include/atomfirmware.h:1051
- sys/dev/drm/amd/display/include/grph_object_ctrl_defs.h:378
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.
No comments yet.