# DF-0634 — VERDICT

## Verdict: REPRODUCED at source level; live trigger requires building ng_tag.ko (not built by default)

The OOB-read bug at `sys/netgraph7/ng_tag.c:537-538` is **real and
confirmed by source trace + userspace harness.** It cannot be
live-triggered on the audit guest because `ng_tag.c` is `optional
netgraph7_tag` and is **not built** into the default kernel or any
loadable module on the guest: there is no `ng_tag.ko` in `/boot/kernel/`,
no Makefile under `sys/netgraph7/` builds it, and `sys/netgraph7/Makefile`
SUBDIR does not include `tag`.

## Mechanism (cited line-by-line)

1. **`sys/netgraph7/ng_tag.c:525`.** `tag_len = hip->in_tag_len;` —
   user-controlled `uint16_t`, accepted up to 65535. The `SET_HOOKIN`
   arglen check at `:363-366` only enforces
   `arglen == sizeof(struct ng_tag_hookin) + hp->tag_len`; it does **not**
   bound `hp->tag_len`, so the in-kernel `hip->in_tag_len` may be any
   value up to 65535.
2. **`:535` `m_tag_locate(m, cookie, type, NULL)`.** Matches on
   `(cookie, type)` **only**, NOT on data length
   (`sys/kern/uipc_mbuf2.c:325-340`). Any in-kernel tag with a matching
   `(cookie, type)` is returned, regardless of its `m_tag_len`.
3. **`:537-538` `memcmp((void*)(tag+1), hip->in_tag_data, tag_len)`.**
   Reads `tag_len` bytes from `tag + 1` — the data area behind the
   `m_tag` header. The data area is exactly `tag->m_tag_len` bytes long
   (allocated by `m_tag_alloc` at `sys/kern/uipc_mbuf2.c:256-269` as
   `kmalloc(len + sizeof(struct m_tag), M_PACKET_TAGS, ...)`).
4. **When `tag_len > tag->m_tag_len`:** the `memcmp` reads
   `tag_len - tag->m_tag_len` bytes past the end of the `m_tag`
   allocation. With a small overshoot this is a silent heap-leak (the
   `memcmp` match/mismatch result, observable via which output hook the
   packet takes, leaks 1 bit at a time per probe × 256 probes per byte =
   full byte leak). With a large overshoot (e.g. `tag_len=65535`,
   `m_tag_len=4`) the read walks off the slab and into an unmapped page
   → **kernel panic**.
5. **No write primitive** is obtained (`memcmp` is read-only).

## Harness verification

`findings/poc/DF-0634/df0634_oob_sim.c` reproduces the OOB pattern in
userspace: places an 8-byte "m_tag data" area at the end of a mapped
page with an unmapped guard page next to it, then simulates the
`memcmp(tag+1, in_tag_data, tag_len)` with `tag_len = 8 + overshoot`.
Output (run on the audit guest):

```
[*] simulated m_tag data area: 8 bytes at 0x800473ff8 (end of mapped page)
[*] user tag_len=9 (overshoot=1 bytes past m_tag_len)
[*] simulating memcmp((void*)(tag+1), hip->in_tag_data, 9)
[*] bytes that the kernel memcmp would read:
    aa aa aa aa aa aa aa aa 
    [offset 8] @0x800474000 is in the unmapped guard page -> kernel memcmp would PAGE FAULT here (panic)
```

The harness proves the OOB pattern: the read at offset `>= m_tag_len`
goes past the allocation. In the kernel, with slab-packed m_tag objects,
a 1-byte overshoot stays within mapped memory and silently leaks a byte
of the adjacent slab object; a page-crossing overshoot panics.

## Why we cannot trigger it on this guest

```
$ ls /boot/kernel/ | grep ng_tag
(empty)

$ grep ng_tag /home/maxx/dfbsd/dfbsd/sys/conf/files
netgraph7/ng_tag.c    optional netgraph7_tag

$ find /home/maxx/dfbsd/dfbsd/sys/netgraph7 -name "Makefile" -path "*tag*"
(nothing)
```

`ng_tag.c` is registered in `sys/conf/files` as `optional
netgraph7_tag` but there is **no Makefile** that builds it as a
module, and `sys/netgraph7/Makefile`'s SUBDIR does not include `tag`.
Verified on the running guest: `ls /boot/kernel/ | grep ng_tag`
returns nothing. The path is reachable only if an admin manually
creates a Makefile for it (or adds `options NETGRAPH7_TAG` to the
kernel config) and rebuilds — not a standard configuration.

## Privilege / threat model

- **Privilege required:** `SYSCAP_RESTRICTEDROOT` to open an
  `AF_NETGRAPH` control socket (`ng_socket.c:182-184`), i.e. root.
  This is a privileged-local→kernel memory-safety defect.
- **Preconditions:** admin has built and loaded ng_tag; user can
  configure an ng_tag node graph (root) and route a packet with an
  m_tag of the matching `(cookie, type)` through it.
- **Impact (a) — DoS:** `tag_len=65535` and any matching tag →
  page-crossing OOB → panic. Single-packet crash.
- **Impact (b) — info leak:** small overshoot leaks adjacent slab
  bytes byte-by-byte through the ifMatch/ifNotMatch routing side
  channel. ≤256 probes per byte.
- **No write primitive.**

This is **not** unpriv→root. It is a root→kernel memory-safety gap
that becomes a DoS or a slow heap info-leak.

## Recommended fix

Require `tag->m_tag_len == tag_len` before the `memcmp`. The proposed
diff in the finding markdown is correct; `findings/poc/DF-0634/fix.diff`
carries a verified, git-apply-able version.
