DF-5629 / verdict.json
{ "finding_id": "DF-5629", "status": "reproduced", "verdict": "Unprivileged local kernel heap info leak: extract_interpreter() (sys/kern/imgact_elf.c:1817-1863) never NUL-terminates the kmallocd PT_INTERP payload, so uprintf(\"ELF interpreter %s not found\", interp) at imgact_elf.c:844 walks past the chunk and prints adjacent kernel heap bytes to the executing users tty. Reproduced 3/3 runs (deterministic 23 bytes/run: stale kernel M_TEMP strings such as tmp/df5629/make_interp2 and /tmp/df5629), plus an ungroomed 2-byte sample (ff ff). Reachable by any user via execve of a self-owned crafted ELF (OSABI=0 brands DragonFly via the interp/brand config wired in sys/cpu/x86_64/misc/elf_machdep.c:58-68). Fix validated on a rebuilt kernel: binary now ENOEXEC, 0 leak lines, no regression.", "reproduced": true, "impact": "leak", "confidence": "certain", "exploit_chain": "execve(crafted ELF with 1000-byte non-NUL PT_INTERP, uid 1001) -> brand match via EI_OSABI==ELFOSABI_NONE (imgact_elf.c:569-573) -> kmalloc(1000) unterminated (imgact_elf.c:668, 1817-1863) -> load_file(interp) fails -> uprintf %s walks past chunk (imgact_elf.c:844) -> stale heap bytes of the recycled kmalloc-1024 chunk (bytes 1000..1023 + neighbor run until NUL) printed to the users controlling terminal; leak length heap-layout dependent and groomable (setsockopt kmalloc+copyin groomer in pack).", "evidence": "run.log (size matrix 8 execs/size: size=1000 8/8 runs leaked, 184 bytes total; final 3 samples: extra=23 bytes each); leak_sample.txt + fline1-3.txt (hex of leaked bytes: kernel path strings); tty1.txt (raw transcript); VERDICT.md fix-validation section (patched kernel: 0 leak lines, ENOEXEC).", "kernel_refs": [ "sys/kern/imgact_elf.c:1817", "sys/kern/imgact_elf.c:1838", "sys/kern/imgact_elf.c:844", "sys/kern/imgact_elf.c:663", "sys/kern/imgact_elf.c:569", "sys/cpu/x86_64/misc/elf_machdep.c:58", "sys/cpu/x86_64/misc/elf_machdep.c:63" ], "poc_changes": "Seed concept only; authored full builder+trigger from scratch: make_interp2.c parameterizes PT_INTERP payload size (empirical matrix found size=1000 gives deterministic leak: kmalloc(1000) leaves 24 stale in-chunk bytes); run_leak.c execve trigger run under script(1) so uprintf lands on a capturable pty; groom.c (setsockopt kmalloc+copyin at uipc_syscalls.c:1257) for heap seeding.", "attempts": 84, "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 (baseline) / #1 patched build Oct 8 23:30:36 UTC 2026", "runtime_sec": 900, "guest_dirty": 0, "build_cmd": "cc -O -o make_interp2 make_interp2.c && cc -O -o run_leak run_leak.c && ./make_interp2 final.bin 1000 128 && chmod 755 final.bin", "run_cmd": "su -m maxx -c script -q tty.txt ./run_leak ./final.bin 1; grep -ac ELF interpreter tty.txt # expect >=1 with bytes after the 1000-byte payload", "code_hash": "873d4b71927a427ff54e3639a0b6f6dc02c048e03eb1c2c5edccae9cc6c7184d", "notes": "p_filesz must be <= min(PAGE_SIZE, MAXPATHLEN=1024) (imgact_elf.c:663-666); exact-1024 payloads usually leak 0 visible bytes (neighbor first byte tends to 0x00) - size=1000 is the deterministic configuration. KLD not needed for this finding.", "recommended_fix": "extract_interpreter(): reject PT_INTERP payloads that are not NUL-terminated within p_filesz before returning success (see fix.diff).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Patched kernel (nativekernel rebuild with fix.diff, kernel #1): same binary -> ENOEXEC, 0 ELF interpreter lines on the pty (baseline: 1 line + 23 leaked heap bytes); normal NUL-terminated interpreters unaffected (system boots/runs).", "fix_evidence": "run.log DF-5629 fix validation section; VERDICT.md; fix.diff in pack." } |