DF-4194 / verdict.json
{ "finding_id": "DF-4194", "status": "untested", "reproduced": 0, "impact": "panic", "confidence": "certain", "verdict": "Source-certain ERR_PTR/NULL contract mismatch. drm_mode_create_tile_group() (sys/dev/drm/drm_connector.c:1917,1929) returns ERR_PTR on allocation failure, but its only caller drm_parse_tiled_block() (sys/dev/drm/drm_edid.c:5141) checks only if (!tg) \u2014 the NULL contract upstream Linux v5.2 actually implements (verified against git.kernel.org v5.2: both failure paths return NULL). The ERR_PTR passes the check and is stored as connector->tile_group (drm_edid.c:5150) with connector->has_tile already true (drm_edid.c:5120). Four later sites dereference or kref_put the poison pointer at fixed unmapped addresses (0xfffffffffffffff4 write via kref_put at drm_edid.c:5148/5218 and drm_connector.c:388; 0x4 read via ->id at drm_connector.c:1490 and drm_fb_helper.c:2388) -> deterministic kernel page fault/panic on the next EDID probe, sink change, MST property update, or driver unload. Kernel-level reproduction was not possible on the audit guest: it has no DRM device at all (/dev/dri absent, no drm/i915 kmods, only QEMU virtio-vga \u2014 env.txt), so the EDID/DisplayID path cannot be exercised; trigger.c instead demonstrates the exact pointer arithmetic on the guest (run.log).", "exploit_chain": "malicious monitor/dock/VM-display EDID with a DisplayID TILED_DISPLAY block + GFP_KERNEL alloc failure under (locally aggravatable) kernel memory pressure -> drm_get_edid -> drm_get_displayid -> drm_parse_tiled_block stores ERR_PTR in connector->tile_group -> next probe / sink change / unload / MST set_tile_property dereferences or kref_puts (struct drm_tile_group*)-12 -> page fault at 0xfffffffffffffff4 or 0x4 -> kernel panic (local DoS; fixed-address fault, no corruption primitive).", "evidence": "VERDICT.md crash-site table; run.log (guest-computed fault addresses 0xfffffffffffffff4 and 0x4 from the real struct layout); env.txt (no DRM device on guest); fix.diff (git apply --check clean vs sys/); upstream v5.2 comparison quoted in VERDICT.md.", "kernel_refs": [ "sys/dev/drm/drm_connector.c:1917", "sys/dev/drm/drm_connector.c:1929", "sys/dev/drm/drm_edid.c:5141", "sys/dev/drm/drm_edid.c:5150", "sys/dev/drm/drm_connector.c:388", "sys/dev/drm/drm_connector.c:1490", "sys/dev/drm/drm_edid.c:5148", "sys/dev/drm/drm_edid.c:5218" ], "poc_changes": "No seed PoC existed; authored trigger.c from scratch. First guest build hit a missing <errno.h> include (ENOMEM undeclared) \u2014 fixed; rebuilt clean, rc=0, zero warnings.", "attempts": 2, "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": 8, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/DF-4194-trigger /tmp/DF-4194-trigger.c", "run_cmd": "/tmp/DF-4194-trigger", "code_hash": "3b7a3f3a7a4fbd89d8261cc888c5f8afe71b4fae9389c2f1e3e1e3dd91934ed0", "fix_status": "not_tested", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff restores the upstream v5.2 NULL contract (both failure paths return NULL); the callers existing if (!tg) return -ENOMEM; then fires and drm_get_displayids out_drop_ref clears has_tile/tile_group exactly as upstream. git apply --check passes vs the read-only sys/ tree. Kernel-build validation was not performed: the guest has no DRM hardware, so neither baseline nor patched kernel can exercise the path; the change is the literal upstream fix.", "fix_evidence": "findings/poc/DF-4194/fix.diff; upstream v5.2 source excerpt in VERDICT.md", "notes": "Hardware-gated (needs a DRM connector + crafted DisplayID-tiled EDID + alloc failure). Pass-2 find: contract divergence between the ports ERR_PTR return and the upstream-identical NULL-checking caller. Trigger conditions and aggravation vector (local memory pressure) documented in VERDICT.md. Demo artifacts left only in guest /tmp; guest otherwise untouched.", "recommended_fix": "Make drm_mode_create_tile_group() return NULL (not ERR_PTR) on both failure paths, matching upstream Linux v5.2 and the callers existing NULL check." } |