# DF-5833 — coredump note-size precompute race → swallowed putallnotes error → size_t-underflowed vnh_max → unbounded cb_put_fp heap overflow

| Field | Value |
|---|---|
| Finding | LWP-set mutation between generic_elf_coredump's DRYRUN sizing pass and WRITE pass |
| File | sys/kern/imgact_elf.c |
| Lines | 1012/1023 (two-pass sizing), 1321-1322 (swallowed error), 1422 (in-window kmalloc drops p_token), 1653-1655 (vnh_max underflow), 1113-1185 (cb_put_fp writes) |
| Bucket | memcorrupt |
| Guest | DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), QEMU/KVM 4GB/6vCPU |

## What the bug is (code chain, all verified line-by-line)

1. `generic_elf_coredump()` sizes the core header with a **DRYRUN** pass
   (`__elfN(puthdr)(…, DRYRUN, …)`, imgact_elf.c:1012), allocates
   `target.buf = kmalloc(target.off_max, M_TEMP, M_WAITOK|M_ZERO)`
   (imgact_elf.c:1023) and then re-runs the header generation in
   **WRITE** mode (imgact_elf.c:1025 → :1293).
2. `elf_putallnotes()` sizes its NT_PRSTATUS/NT_FPREGSET notes from the
   **live LWP set** (`FOREACH_LWP_IN_PROC`, imgact_elf.c:1480). The two
   passes run at different times; in WRITE mode the pass starts with a
   fresh `kmalloc(sizeof(*tmpdata), M_TEMP, M_ZERO|M_WAITOK)`
   (imgact_elf.c:1422). `sigexit()` holds `p->p_token` across the dump
   (kern_sig.c:2385-2423), but DragonFly LWKT tokens are **released
   whenever the holding thread blocks** (`lwkt_switch` →
   `lwkt_relalltokens`, lwkt_thread.c:641-643) — so every blocking point
   between the two walks (the `off_max` kmalloc, the `tmpdata` kmalloc,
   any fp_write) opens a window in which a sibling LWP can mutate the
   LWP tree. `proc_stop(SCORE)`'s pre-counted "stopped" LSSLEEP LWPs
   (kern_sig.c:1598-1612) are exactly such siblings: they are mid-syscall
   and continue running when woken.
3. If LWPs are **inserted** between the passes (lwp_create →
   `lwp_rb_tree_RB_INSERT`, kern_fork.c:848), the WRITE pass needs
   800 bytes/LWP more than reserved. `target_reserve()` returns NULL,
   sets `*error = EINVAL`, **but still advances `target->off`**
   (imgact_elf.c:1268-1274), pushing `off` past `off_max`.
4. `__elfN(puthdr)` **discards** `elf_putallnotes()`'s return value
   (imgact_elf.c:1321-1322) and keeps going.
5. `elf_puttextvp()` then computes
   `fpc.vnh_max = fpc.vnh + (target->off_max - target->off)/sizeof(vn_hdr)`
   (imgact_elf.c:1653-1655). With `off > off_max` the subtraction
   **size_t-underflows**, making `vnh_max` astronomically large, and
   `each_segment(p, cb_put_fp, &fpc, 0)` writes one 120-byte `vn_hdr`
   per vnode-backed mapping starting **past the end of the kmalloc'd
   header buffer** (imgact_elf.c:1113-1185). The write length is
   attacker-chosen (number of mmap'd-file segments), the record
   contents are attacker-influenced (p_vaddr/p_filesz are the mmap
   addresses/sizes; the file handle comes from VFS_VPTOFH of
   attacker-created files).
6. Bonus bug on the same path: `error = each_segment(...)` at
   imgact_elf.c:1656 **overwrites** the reserve-failure EINVAL, so
   `elf_puttextvp` returns success even after the overrun.

## What was reproduced on the guest (uid 1001, unprivileged)

Rig: `eater` (pins VM free pages below the allocation floor),
`victim` (200 sleeper threads + 16 worker threads looping
`lwp_create(2)` so their calls park mid-creation on M_WAITOK
allocations under the floor), `dripper` (pre-forked; frees memory in
controlled bursts into the parked allocators), `controller.sh`
(orchestrates probe/tune/attempt loops). Trigger: SIGSEGV path
(initially) and `sys_checkpoint(CKPT_FREEZE)` (kern.ckptgroup set to
-1 by root for rig determinism — the unprivileged sigexit path drives
the identical generic_elf_coredump code).

Reproduced, 20+ times across runs (run.final.log):
- **The LWP set grows during the dump.** Worker threads pre-counted as
  "stopped" by `proc_stop(SCORE)` keep completing `lwp_create` calls
  and inserting LWPs throughout the dump: children counts grow from the
  pre-dump snapshot → DRYRUN → WRITE → post-dump by 14-29 per attempt
  (e.g. attempt 1: snapshot 3586, post-dump 3639, WRITE notes 3822 vs
  3803 expected pre-dump LWPs).
- **The two sizing passes see different LWP sets.** A fully-parsed
  "successful" image (parse2 output) shows `WRITE_end_raw` consuming
  the DRYRUN reservation down to its **final 112 bytes** — i.e. a
  DRYRUN→WRITE delta of 1-6 notes absorbed by the round_page slack;
  deltas beyond the slack were observed as:
  - **CKPT-RESULT rv=-1 errno=22 (EINVAL), no image** (ckpt2 v2
    attempt 4, call-time 93 children / 3 in-flight workers): the WRITE
    pass hit the reservation wall mid-walk — the size-precompute
    mismatch materialized as the swallowed-error failure.
- The endgame heap-overflow write itself (delta ≥ ~7 simultaneous
  in-window inserts) did **not** demonstrably fire in the rig's
  attempts: the header-allocation page starvation during the
  DRYRUN→WRITE window and the p_token endgame race bias the dumper on
  this configuration. The chain up to and including the precondition
  (mid-dump LWP insertion + reservation overrun) is reproduced; the
  unbounded write is established by line-level analysis
  (imgact_elf.c:1653-1655 arithmetic is unconditional).

## PoC files

- `eater.c`, `victim.c`, `dripper.c`, `controller.sh`, `parse_core.c`,
  `parse2.c` — the full rig (build: `cc -O2 -o X X.c [-lpthread]`)
- `run.final.log` — the final 6-attempt run + image parse analysis
- `fix.diff` — error propagation + underflow guard

## Fix validation

Not performed (kernel rebuild): the heap-overflow write did not
demonstrate on-guest within the rig, so there is no observable
regression to A/B. The fix.diff removes both enabling defects
(swallowed error; underflow-prone bound) by construction.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
