{
  "finding_id": "DF-4827",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "likely",
  "verdict": "Deterministic wild-write panic reproduced in-kernel: the probe KLD compiles the REAL unmodified audited dce_i2c_sw.c (guest /usr/src/sys sha256 75d99a9e... == audit tree) with the amdgpu modules own -Werror flags and calls the real dce_i2c_acquire_i2c_sw_engine() on a faithfully-constructed resource_pool + ddc (pin_data->en=GPIO_DDC_LINE_DDC_VGA=6, hw_supported=true). The real get_hw_supported_ddc_line accepts line 6 (6 < GPIO_DDC_LINE_COUNT==8), reads pool->sw_i2cs[6] OUT OF BOUNDS (array is MAX_PIPES==6, sw_i2cs[6] aliases {i2c_hw_buffer_in_use,pad,pipe_count} -> 0x0000000600000000, non-NULL), and the real engine->ddc = ddc (dce_i2c_sw.c:392) faults: Fatal trap 12 ... fault virtual address = 0x600000000 ... supervisor write data ... Stopped at dce_i2c_acquire_i2c_sw_engine+0x59: movq %rbx,(%r12). Only the GPIO-service layer is stubbed; its success for en=6 is source-documented (hw_factory_dce80.c:163 number_of_pins[DDC_DATA]=8, hw_translate_dce80.c:190 DDCVGA->en 6, gpio_service.c:224-290). The full hardware entry (amdgpu probe -> dce_i2c_submit_command on a VGA/I2C-pad DDC link) is gated on AMD hardware absent from this guest; en and the HW-cap bit come from the GPU VBIOS (bios_parser2.c:395-405), so the real-world trigger is a crafted/mis-declared VBIOS (flashed card, passthrough ROM) or a board legitimately mapping DDC to the VGA pad - every DM ASIC reaches this code (dce_i2c.c:46-58, SW engine tried first). Fix validated BEHAVIORALLY: with fix.diff (line < pool->pipe_count, mirroring dce_i2c_hw.c:365) the same call returns NULL cleanly, no dal_ddc_open, no fault, module load/unload clean, guest stays up.",
  "exploit_chain": "VBIOS-controlled preconditions: I2C record with I2C_HW_CAP whose DDC clock pin register is mmDC_GPIO_DDCVGA_A (or I2CPAD) -> ddc_pin->pin_data->en = 6/7 with hw_supported=1 (dc_link_ddc.c:266-276) -> any I2C transfer on that link (hotplug/EDID detect, or DRM_IOCTL i2c via amdgpu_dm_i2c_xfer:3983-4016, in-kernel unprivileged-forced) -> dc_submit_i2c -> dce_i2c_submit_command (SW first, dce_i2c.c:46-58) -> dce_i2c_acquire_i2c_sw_engine: guard passes (line < GPIO_DDC_LINE_COUNT=8), OOB read sw_i2cs[6] = (pipe_count<<32)|flag (non-NULL) -> dal_ddc_open OK -> engine->ddc = ddc writes a kernel pointer to ~0x2-0x6_00000000 (fixed per-ASIC, canonical, unmapped) -> page fault/panic (reliable DoS); if the aliased address were mapped this is silent cross-object corruption of arbitrary kernel memory. Realistic ceiling: local DoS with VBIOS precondition (uid0 not practical - write address is nearly fixed by pipe_count, not groomable).",
  "evidence": "run.log (baseline panic: OOB read 0x0000000600000000, Fatal trap 12, VA=0x600000000, Stopped at dce_i2c_acquire_i2c_sw_engine+0x59: movq %rbx,(%r12) == engine->ddc = ddc at dce_i2c_sw.c:392); run.2.log (patched: swi2cs: acquire returned 0 (NULL = correctly rejected), KLDLOAD2_OK + KLDUNLOAD_OK, guest up); panic.txt; build.log/build.2.log (-Werror builds of the real audited file, baseline + patched); env.txt (uname, cc 8.3); fix.diff",
  "kernel_refs": [
    "sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c:91-92",
    "sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c:536",
    "sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c:392",
    "sys/dev/drm/amd/display/dc/inc/core_types.h:151",
    "sys/dev/drm/amd/display/dc/inc/hw/hw_shared.h:37",
    "sys/dev/drm/amd/display/include/gpio_types.h:206-220",
    "sys/dev/drm/amd/display/dc/dce/dce_i2c.c:46-58",
    "sys/dev/drm/amd/display/dc/dce/dce_i2c_hw.c:365",
    "sys/dev/drm/amd/display/dc/gpio/dce80/hw_factory_dce80.c:163-164",
    "sys/dev/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c:190-196",
    "sys/dev/drm/amd/display/dc/bios/bios_parser2.c:395-405",
    "sys/dev/drm/amd/display/dc/core/dc_link_ddc.c:266-276"
  ],
  "poc_changes": "wrote the KLD from scratch (swi2cs_probe.c + Makefile): no seed PoC existed. Three iterations to a clean build: (1) plain sys/* includes clash with -include kconfig.h -> rewrote as linux-compat module (linux/module.h) like real amdgpu files; (2) gcc -Werror=array-bounds rejected the probes own &pool.sw_i2cs[6] subscript (the compiler itself confirming the OOB) -> char* arithmetic; (3) generated fix.diff with real diff -u after a hand-written hunk failed patch(1).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly v6.5.0.1712.g89e6a-DEVELOPMENT #1 x86_64 (kernel build DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC)",
  "runtime_sec": 1500,
  "guest_dirty": 0,
  "build_cmd": "guest: cd /tmp/df4827 && make   (swi2cs_probe.c + REAL /usr/src/sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c, amdgpu module CFLAGS, -Werror)",
  "run_cmd": "guest: kldload -v /tmp/df4827/swi2cs.ko   (baseline: panic at dce_i2c_acquire_i2c_sw_engine+0x59; patched: clean NULL return + kldunload)",
  "code_hash": "cf8a060f7b43daa295538146f9c00746fea1eceb2bca88b22ba4e2702079267f",
  "notes": "Guest reset (with-src) performed after the run - clean for the next spawn. gcc itself proves the OOB (-Werror=array-bounds on the direct subscript). The panic was captured twice (initial run + run.sh phase 1). dce_i2c_hw.cs identical-shape index is SAFE (guarded by pipe_count, dce_i2c_hw.c:365) - only the SW path (this file) is wrong; old i2caux arrays are [GPIO_DDC_LINE_COUNT] so its guard is correct there. Severity rationale: firmware-table-driven wild write into a driver flow reachable without privileges once the VBIOS preconditions hold; realistic impact ceiling is reliable kernel panic (DoS).",
  "recommended_fix": "Bound the sw_i2cs[] index by the array domain, not GPIO_DDC_LINE_COUNT: if (get_hw_supported_ddc_line(ddc, &line) && line < pool->pipe_count) engine = pool->sw_i2cs[line]; (mirrors dce_i2c_hw.c:365); see fix.diff",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (line < pool->pipe_count guard, mirroring the HW paths existing idiom at dce_i2c_hw.c:365) applies clean to the guests dce_i2c_sw.c (Hunk #1 succeeded at 532), rebuilds with -Werror (build.2.log compiles the patched real file), and BEHAVIORALLY fixes the panic: the identical probe call that faulted on baseline now prints swi2cs: acquire returned 0 (NULL = correctly rejected) with no dal_ddc_open side effect, module loads AND unloads cleanly, guest stays up (run.2.log, vm.sh status: up). Line 6/7 DDC handles now gracefully fall back: acquire returns NULL, dce_i2c_submit_command proceeds to the HW engine which equally rejects, and the I2C call fails with an error instead of corrupting memory.",
  "fix_evidence": "fix.diff; run.2.log (acquire returned 0 (NULL = correctly rejected), KLDLOAD2_OK, KLDUNLOAD_OK); build.2.log (patched real dce_i2c_sw.c compiled -Werror, link of swi2cs.ko)"
}