DF-2771 / verdict.json
{ "finding_id": "DF-2771", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "mapsize accumulation in link_elf_obj_load_file (sys/kern/link_elf_obj.c:621-633) has no overflow check on attacker-chosen sh_size values; two NOBITS sections sized 0xFFFFFFFFFFFFF000 and 0x2000 wrap the 64-bit total to 0x1010, so only a 2-page KVA mapping is allocated (:640-667) while the load loop then executes bzero(mapbase+16, 2^64-0x1000) (:760) โ an uncontrolled zero-fill that runs past the mapping until the first unmapped page. Reproduced on the stock guest as 'Fatal trap 12: supervisor write data, page not present' stopped at memset+0xd5 (repe stosq), fault VA = first page past the mapping. The write value (zero) and stop point are not controllable, so the realistic ceiling is kernel-memory wipe of anything mapped above the module followed by certain panic; trigger is root-supplied (kldload is SYSCAP_NOKLD-gated), so no unprivileged reach โ impact honestly recorded as panic. fix.diff caps sh_size at LINKER_MAX_SECSIZE (1<<44) in the sizing loop; validated on a rebuilt kernel (#1 Sep 1 2026): same PoC now fails cleanly with 'kldload: section too large', no panic, guest survives.", "exploit_chain": "root crafts 554-byte ET_REL module (gen_module.py mode=mapsize-wrap) -> kldload -> link_elf_obj_load_file sizes mapping at wrapped 0x1010 -> bzero of 2^64-0x1000 bytes at the 2-page mapping -> supervisor-write page fault -> kernel panic. No uid0 route: uncontrolled zero-fill only, and the input channel requires root (SYSCAP_NOKLD), so escalation is out of scope for this class.", "evidence": [ "panic.txt โ baseline serial-console excerpt: Fatal trap 12, supervisor write data page not present, stopped at memset+0xd5 repe stosq, fault VA 0xffffffff82602000 (= mapping end + 0)", "run.log โ baseline (panic, ssh hang) vs patched ('section too large', RC=1, guest up) transcripts", "gen_module.py โ trigger generator (mode mapsize-wrap); shdr[5] NOBITS size=0xfffffffffffff000, shdr[6] NOBITS size=0x2000, both align=1", "build.log โ full 37903-line untrimmed nativekernel build of the fix (link_elf_obj.o compiled -Werror clean)", "VERDICT.md โ full path:line trace, primitive characterization, negative space (alignmask truncation, nbytes int math, unwrapped-huge case)", "fix.diff โ LINKER_MAX_SECSIZE cap validated by baseline-vs-patched rerun" ], "kernel_refs": [ "sys/kern/link_elf_obj.c:621", "sys/kern/link_elf_obj.c:630", "sys/kern/link_elf_obj.c:640", "sys/kern/link_elf_obj.c:663", "sys/kern/link_elf_obj.c:760" ], "poc_changes": "n/a โ trigger authored fresh for this finding (hand-crafted ET_REL, no prior seed); generator supports three modes, this pack uses mapsize-wrap", "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": 1500, "guest_dirty": 0, "build_cmd": "python3 gen_module.py df2771.ko mapsize-wrap", "run_cmd": "vm.sh run_root 'kldload /tmp/df2771.ko' (expect Fatal trap 12 in memset on stock; 'section too large' RC=1 on patched)", "code_hash": "352c5bb633e0445b61c03e12fee9bc20428b01d5c4debe40506de0c2410120d2", "notes": "Fix validated with a single combined kernel build covering DF-2771/2772/2773 (same fix.diff in all three packs). Guest reset to clean-source snapshot after verification. Adjacent-mapping corruption is theoretically possible if kernel_map placed a live mapping directly above the module's, but the write remains a blind zero-fill terminating in the same fatal fault โ no control primitive. Severity calibrated Low per DF-0040/0041/0042 precedent (root-supplied module input).", "recommended_fix": "Cap sh_size in the mapsize sizing loop (LINKER_MAX_SECSIZE = 1<<44) and reject the module with ENOEXEC; keeps the u64 accumulator from wrapping so the mapping always covers every per-section bzero/read length.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 1 01:04:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched kernel rejects the identical module with 'kldload: /tmp/df2771.ko: section too large' (ENOEXEC, RC=1), no panic, guest stays up; baseline kernel #0 panics on the same input. fix.diff authored against read-only sys/ and applied only inside the guest's /usr/src copy.", "fix_evidence": "run.log (baseline vs patched), build.log (kernel #1 build), dmesg line 'kldload: /tmp/df2771.ko: section too large'" } |