DF-2773 / verdict.json
{ "finding_id": "DF-2773", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "link_elf_obj_load_file's scan loop (sys/kern/link_elf_obj.c:517-539) increments ef->nreltab/nrelatab before the tracking arrays are allocated (:557-572); several rejections in between (nprogtab==0 at :540, nsym!=1 at :545, bad symstrindex at :551) goto out to linker_file_unload, which for a fresh refs==0 file with no modules tears straight through to ops->unload (kern_linker.c:549), where link_elf_obj_unload_file dereferences the NULL arrays in unguarded loops (:887-892). A 434-byte crafted ET_REL module with a symtab+strtab+one .rela and zero PROGBITS sections panics the stock guest: console shows 'kldload: /tmp/df2773.ko: file has no contents' immediately followed by 'Fatal trap 12: page fault, fault virtual address = 0x0, supervisor read data' stopped at link_elf_obj_unload_file+0x6e (movq (%rax),%rdi) โ the error path of the loader itself is the killer. NULL read only: no write, no disclosure, root-gated (SYSCAP_NOKLD) โ Low, consistent with DF-0040/0041/0042. fix.diff guards both loops with if (ef->reltab)/if (ef->relatab); on rebuilt kernel #1 the same module is rejected cleanly (RC=1, guest up, 0 panics).", "exploit_chain": "root crafts module (gen_module.py mode=nrel-nullderef: no PROGBITS + one .rela) -> kldload -> scan counts nrelatab=1, arrays unallocated -> 'file has no contents' ENOEXEC path -> linker_file_unload -> link_elf_obj_unload_file reads ef->relatab[0].rela via NULL -> fatal page fault at 0x0 -> kernel panic. NULL-deref read only; no unprivileged reach (SYSCAP_NOKLD); ceiling is deterministic panic/DoS.", "evidence": [ "panic.txt โ baseline serial console: 'file has no contents' then Fatal trap 12 VA=0x0 stopped at link_elf_obj_unload_file+0x6e movq (%rax),%rdi", "run.log โ baseline (panic) vs patched (clean RC=1 rejection) transcripts", "gen_module.py โ trigger generator (mode nrel-nullderef): 5 sections, zero PROGBITS/NOBITS, one SHT_RELA", "build.log โ full untrimmed nativekernel build of the fix (-Werror clean)", "VERDICT.md โ path:line trace incl. all four abort paths between scan and allocation and the kern_linker.c:549 teardown", "fix.diff โ unload-loop NULL guards (plus the companion DF-2771/2772 changes), baseline-vs-patched validated" ], "kernel_refs": [ "sys/kern/link_elf_obj.c:530", "sys/kern/link_elf_obj.c:534", "sys/kern/link_elf_obj.c:540", "sys/kern/link_elf_obj.c:557", "sys/kern/link_elf_obj.c:832", "sys/kern/link_elf_obj.c:887", "sys/kern/link_elf_obj.c:890", "sys/kern/kern_linker.c:549" ], "poc_changes": "n/a โ trigger authored fresh (hand-crafted ET_REL); no prior seed for this finding", "attempts": 1, "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": 600, "guest_dirty": 0, "build_cmd": "python3 gen_module.py df2773.ko nrel-nullderef", "run_cmd": "vm.sh run_root 'kldload /tmp/df2773.ko' (expect 'file has no contents' then Fatal trap 12 VA=0x0 at link_elf_obj_unload_file on stock; clean RC=1 rejection on patched)", "code_hash": "352c5bb633e0445b61c03e12fee9bc20428b01d5c4debe40506de0c2410120d2", "notes": "Fix validated with a single combined kernel build covering DF-2771/2772/2773. Guest reset to clean-source snapshot after verification. The defect also means ANY future scan-time hardening of the REL/RELA counting (e.g. DF-2772's fix) would re-open this NULL-deref without the unload guard โ the two fixes are coupled.", "recommended_fix": "Guard the entry-free loops in link_elf_obj_unload_file with if (ef->reltab) / if (ef->relatab), mirroring the existing if (ef->progtab) guard.", "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 ENOEXEC (RC=1, no panic, guest up); baseline kernel #0 NULL-deref 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 'linker_load_file: Unsupported file type'" } |