# DF-0050 — VERDICT

**Verdict:** REPRODUCED (info leak) — and the fix is VALIDATED.

## The bug

`sys_msgctl(IPC_STAT)` at `sys/kern/sysv_msg.c:324` does a verbatim
`copyout(msqptr, user_msqptr, sizeof(struct msqid_ds))`. `struct msqid_ds`
(`sys/sys/msg.h:68-84`) carries two kernel-internal pointers,
`struct msg *msg_first` (`:70`) and `*msg_last` (`:71`), that are populated
with live kernel heap addresses by `msgsnd` at `sysv_msg.c:761-768`. After a
queue has any message queued, an unprivileged `msgctl(IPC_STAT)` returns
those kernel pointers to userspace.

The boot-time `msqids` array is allocated without `M_ZERO`
(`sysv_msg.c:130`), and `msgget` (`:419-436`) initializes everything except
`msg_pad1..msg_pad4` (`msg.h:78,80,82,83`); the pad fields are never written
and may carry boot-time heap residue. (In this guest they happened to read
zero, but the spec lets them leak.)

## Threat model

Any local unprivileged user: `msgget(IPC_PRIVATE)` + `msgsnd` + `msgctl(IPC_STAT)`.
No privilege, no special config, default GENERIC kernel (`6.5-DEVELOPMENT #0`).

## Reproduction evidence (unpatched #0 baseline)

Single-message queue, 3 runs (deterministic — the `struct msg` headers are
allocated once at boot from a fixed pool, so the same slot is reused):

```
msg_first = 0xfffff8008e0ea980    <- live kernel heap pointer (M_MSG slab)
msg_last  = 0xfffff8008e0ea980
```

Two-message queue (proves BOTH ends of the list leak; addresses differ):

```
first = 0xfffff8008e0ea998  last = 0xfffff8008e0ea980  same=0
first = 0xfffff8008e0ea980  last = 0xfffff8008e0ea998  same=0
```

The `0xfffff800...` upper 16 bits confirm these are in the DragonFlyBSD
kernel-map range (KVA). Cross-checked with kernel symbols (`msghdrs` at
`0xffffffff8131dc88` BSS; the leaked values index into the heap array it
points at). This is a reliable KASLR / kernel-heap-ASLR bypass.

## Impact

Info leak only — no memory corruption, no escalation chain possible. The
primitive is a single kernel heap pointer (`struct msg *`) per call, plus
possibly boot-time heap residue in the pad fields. Useful as an exploit
enabler to convert a separate unreliable kernel memory-corruption bug into
a reliable one. Rated Low (CWE-200 / CWE-909).

## The fix

Sanitize the kernel-internal fields in a local copy before `copyout`:

```c
struct msqid_ds msqout = *msqptr;
msqout.msg_first = NULL;
msqout.msg_last  = NULL;
msqout.msg_pad1  = 0;
msqout.msg_pad2  = 0;
msqout.msg_pad3  = 0;
bzero(msqout.msg_pad4, sizeof(msqout.msg_pad4));
eval = copyout(&msqout, user_msqptr, sizeof(msqout));
```

The finding markdown's `## Recommended fix` proposal matches this exactly
(no supersession). The standalone `git apply`-able diff is in `fix.diff`.

## Fix validation (Phase 8)

Built a single-fix kernel (`make -j6 nativekernel KERNCONF=X86_64_GENERIC`,
rc=0) from the `with-src` base + this one diff, installed it as
`/boot/kernel/kernel`, rebooted, confirmed `kern.version` bumped to
`#1: Mon Jul 13 00:07:02 UTC 2026`.

**Before (unpatched #0):** `msg_first = 0xfffff8008e0ea980`
**After (patched #1):**   `msg_first = 0x0`

Confirmed deterministically across 3 single-msg runs AND the 2-message
queue (both ends now NULL). The fix closes the leak completely. The kernel
also still functions normally (msgsnd/msgctl succeed, exit 0).

## PoC changes

The shipped `msg_leak.c` had nested `/* ... /* ... */ */` block comments
that broke the compiler (inner `/*` terminated the outer comment early,
stray `'` became a token). Rewrote the header comment to use parenthesized
field descriptions instead of inner `/* ... */` blocks, added `#include
<unistd.h>` / `<stdint.h>` / `<sys/types.h>`, hardened the build with
explicit `IPC_RMID` cleanup on error paths, and added pad-field dumps plus
a `LEAK:` classifier that recognizes the `0xffff...` kernel-map signature.
Build: `cc -o msg_leak msg_leak.c`. No runtime/setup changes.

## Reproduce

```
./build.sh && ./run.sh        # as unprivileged maxx
# buggy:  msg_first = 0xfffff800...
# fixed:  msg_first = 0x0
```
