# DF-2776 VERDICT — reproduced (stale-cookie read + console disclosure + panic; fix validated)

## Bottom line

`unregister_int` treats the caller-supplied cookie as live memory before and
after validation. With a stale (freed) cookie the stock kernel:

1. reads `((intrec_t)id)->intr` from freed heap at kern_intr.c:425 — the
   value that selects which `intr_block->ary[cpuid][intr]` list to walk;
2. on the not-found path prints `((intrec_t)id)->name` with `%s`
   (kern_intr.c:489-490) — a NUL-terminated read from whatever address the
   freed cookie now holds.

Reproduced on the stock INVARIANTS guest with the in-kernel rig
(`dfh.run2776`): register -> real unregister (free) -> LIFO reclaim of the
freed intrec (`kmalloc(56)=... reuse=YES`, same address observed) -> fill
`intr=253`, `name=&df_token_2776` -> real `unregister_int(stale)`:

    warning: unregister_int: int 253 handler for DF2776-STALE-COOKIE-READ-4b77d1 not found

The `intr` value 253 came from the freed cookie (nothing else supplied it)
and the printed string came from the chosen kernel address (harness .data).
The wild variant (`name=0x5151515151515150`) panicked the kernel **inside
the kprintf**, console showing the message truncated mid-`%s`:

    warning: unregister_int: int 253 handler for Stopped at      strlen: cmpb    $0,(%rdi)

## Threat model honesty

The trigger requires a kernel-context caller to hand `unregister_int` a
stale cookie — i.e. a driver bug (double unregister, use-after-free of its
own handle). An unprivileged user cannot reach this directly. The impact is
(a) kernel-memory disclosure to the console/log (one NUL-terminated string
per bad call, chosen-address once heap reuse is influenced) and (b) kernel
panic (local DoS) when the stale `name` is invalid. Severity Low as filed
is right.

Note: the `intr` load at :425 cannot be removed without an API change (the
cookie is the only way to find the list); the fix removes the *print-through*
of the stale pointer, which is the disclosure/crash vector.

## Fix validation

`fix.diff` hunk (identical file in all three packs):

    -	kprintf("warning: unregister_int: int %d handler for %s not found\n",
    -		intr, ((intrec_t)id)->name);
    +	kprintf("warning: unregister_int: int handler %p not found\n", id);

On the rebuilt fixed kernel both variants print the pointer only
(`warning: unregister_int: int handler 0xfffff8004f164290 not found`), the
wild variant returns without panicking, and the guest stays alive
(df2776_fixed.dmesg). Stock-vs-fixed A/B is exact: same rig, same sysctl
invocation.

## Kernel references

- sys/kern/kern_intr.c:423-428 (cookie deref before validation)
- sys/kern/kern_intr.c:440-444 (walk selected by freed-sourced intr)
- sys/kern/kern_intr.c:485-491 (not-found path printing through the cookie)

## Rig honesty notes

- The reclaim probe writes the fake cookie fields only into memory it owns
  (the successfully reclaimed block). The stock kernel performs both
  dereferences itself (:425 read observed via the printed `int 253`,
  :490 read observed via the disclosed string / the panic).
- kern_intr.c on the K1 build was byte-identical to stock (diff-verified
  before build); the fixed build is pristine + fix.diff (+ the dormant
  DF-2777 hook lines, which are no-ops unless armed).

## 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.
