ckpt_thaw_proc(): e_phnum == 0 makes kmalloc(0) return ZERO_LENGTH_PTR; phdr->p_filesz reads kernel VA 0x18 (unconditional fatal kernel page fault)
| Field | Value |
|---|---|
| ID | DF-2827 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-476 (near-NULL: ZERO_LENGTH_PTR) |
| File | sys/kern/kern_checkpoint.c |
| Lines | 232-240 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | memcorrupt |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
elf_gethdr() validates e_ehsize/e_phentsize/e_ident but never e_phnum.
With e_phnum == 0, kmalloc(0, M_TEMP, M_WAITOK) returns ZERO_LENGTH_PTR
= (void *)-8, and elf_getnotes(lp, fp, phdr->p_filesz) loads from
0xFFFFFFFFFFFFFFF8 + 0x20 = 0x18 β supervisor page fault β panic on
any kernel config. Requires only a well-formed 64-byte ELF header;
wheel-gated (kern.ckptgroup). The loaded value only feeds the nthreads
sanity check β DoS, no leak. VERIFIED on the stock kernel
(Fatal trap 12 ... fault virtual address = 0x18 ... Stopped at
sys_sys_checkpoint+0x2f0); fixed kernel returns clean EINVAL.
Recommended fix
Reject e_phnum == 0 before the allocation (row diff; validated).
Timeline
- 2026-08-31 Discovered during pass-2 audit of kern_checkpoint.c (GLM 5.3); panic reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2827 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| gen.c | β | 7.4 KB | view raw | |
| trigger_fd3.c | β | 654 B | view raw | |
| build.sh | β | 801 B | view raw | |
| run.sh | β | 175 B | view raw | |
| panic.txt | β | 2.2 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-2827 β ckpt_thaw_proc: e_phnum == 0 β kmalloc(0) = ZERO_LENGTH_PTR deref (kernel page fault)
- File:
sys/kern/kern_checkpoint.c - Bug lines: kern_checkpoint.c:232-240 β
nbyte = sizeof(Elf_Phdr) * ehdr->e_phnumwithe_phnum == 0βkmalloc(0, M_TEMP, M_WAITOK)returnsZERO_LENGTH_PTR=(void *)-8(sys/kern/kern_slaballoc.c:888-891);elf_getphdrs(fp, phdr, 0)reads nothing, thenphdr->p_filesz(kern_checkpoint.c:240) loads from(void*)-8 + 0x20= kernel VA 0x18 β supervisor page fault β fatal trap / panic on any kernel config. - Gate: same as DF-2826 (
kern.ckptgroup, wheel by default; no fhandle or further image content needed β the header alone is enough).
Build / Run (guest, root)
sh /root/df2826/build.sh # builds gen + triggers + images cd /root/df2826 ./gen df2827.ckpt stage2.bin zero # e_phnum = 0 variant ./trigger_fd3 df2827.ckpt
Expected
Fatal user address access from kernel mode from trigger_fd3 at ...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x18
fault code = supervisor read data, page not present
Stopped at sys_sys_checkpoint+0x2f0: movq 0x20(%rax),%rax
Debugger("panic")
(0x18 = ZERO_LENGTH_PTR(-8) + offsetof(Elf64_Phdr, p_filesz)(0x20);
the instruction is exactly the phdr->p_filesz load feeding elf_getnotes.)
Fix
Rejected in ../DF-2826/fix.diff (e_phnum == 0 β EINVAL before the
kmalloc); validated on the patched kernel (no trap, clean EINVAL).
DF-2827 VERDICT β ZERO_LENGTH_PTR dereference on e_phnum == 0
REPRODUCED (certain). ckpt_thaw_proc() validates e_ehsize,
e_phentsize and e_ident in elf_gethdr() (kern_checkpoint.c:117-143)
but never e_phnum. With e_phnum == 0:
nbyte = sizeof(Elf_Phdr) * e_phnum= 0 (kern_checkpoint.c:232);kmalloc(0, M_TEMP, M_WAITOK)hits the degenerate-size case and returnsZERO_LENGTH_PTR=(void *)-8(kern_slaballoc.c:882-891 β "Return a special pointer");elf_getphdrs()reads 0 bytes (harmless);elf_getnotes(lp, fp, phdr->p_filesz)(kern_checkpoint.c:240) evaluatesphdr->p_fileszβ load from0xFFFFFFFFFFFFFFF8 + 0x20= 0x18.
Observed on the stock guest (fresh with-src boot):
Fatal user address access from kernel mode from trigger_fd3 at ffffffff8061e1f0 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x18 fault code = supervisor read data, page not present Stopped at sys_sys_checkpoint+0x2f0: movq 0x20(%rax),%rax
The movq 0x20(%rax),%rax with rax = 0xfffffffffffffff8 is precisely the
phdr->p_filesz read. This is an unconditional kernel-mode fault on a
canonical-low address β panic on any config (no INVARIANTS needed).
Impact: local kernel panic / DoS of the checkpoint path, gated by
kern.ckptgroup (default wheel-only; verified EPERM for a non-wheel user).
No info leak (the loaded value only feeds the nthreads sanity check in
elf_getnotes, kern_checkpoint.c:185-189, which rejects it).
Fix: reject e_phnum == 0 in ckpt_thaw_proc() before the kmalloc β
included and validated with DF-2826's fix.diff (patched kernel returns
EINVAL, no trap; see ../DF-2826/fix_run.log).
Fix verification
fixedWith the e_phnum==0 rejection in the patched kernel, sys_checkpoint returns a clean error (-1/EINVAL per the trigger) and no Fatal trap occurs; kernel stays up. Baseline panic captured in panic.txt.
findings/poc/DF-2827/panic.txt; findings/poc/DF-2826/fix_run.log; findings/poc/DF-2826/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
['panic.txt: Fatal trap 12, fault VA 0x18, instruction movq 0x20(%rax),%rax with rax=(void*)-8', 'fix_run.log (in ../DF-2826): patched kernel returns clean error, no trap', "gen.c: 'zero' mode generates the e_phnum == 0 image"]
PoC changes
Reuses the DF-2826 generator with phnum_mode=zero (e_phnum=0, no phdr table written); trigger identical to DF-2826's fd3 trigger.
Verified recommended fix
Reject e_phnum == 0 (and sanity-bound it) in ckpt_thaw_proc() before the kmalloc - included and validated in ../DF-2826/fix.diff.
Verdict
ckpt_thaw_proc() never validates ehdr->e_phnum. With e_phnum == 0, kmalloc(0, M_TEMP, M_WAITOK) returns ZERO_LENGTH_PTR ((void *)-8) and the subsequent 'phdr->p_filesz' load (kern_checkpoint.c:240) reads kernel VA 0x18, producing an unconditional supervisor page fault: 'Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x18 ... Stopped at sys_sys_checkpoint+0x2f0: movq 0x20(%rax),%rax' (0x20 = offsetof(Elf64_Phdr, p_filesz)). Panic reproduces on the stock kernel from a fresh snapshot; requires only the 64-byte ELF header to be well-formed, no file handles or note content. Gate: kern.ckptgroup (wheel by default); non-wheel users get EPERM before parsing. DoS only - the loaded value feeds the nthreads sanity check in elf_getnotes which rejects garbage.
No comments yet.