# DF-5718 VERDICT

**Status: reproduced (userland differential + guard-page rig). Impact: none
(hardening). Confidence: certain.**

## What was claimed, what was proven

Pass-2 audit of `sys/libkern/strcpy.c` required a census of the unbounded-API
hazard class. The census (all non-contrib `strcpy`/`strcat` call sites in
kern/, net*, netproto/, netgraph*, vfs/, bus/, platform/, dev/, vm/, cpu/)
found every *sized-consumer* path either exact-fit allocated, NUL-forced, or
privilege-gated — with one class of genuine contract violation: the netgraph
text parser **calls `strcpy()` on overlapping regions**:

* `sys/netgraph/netgraph/ng_parse.c:1572` `strcpy(p, p + 1)` — strips the backslash
* `sys/netgraph/netgraph/ng_parse.c:1621` `strcpy(p, v)` — shifts the tail left after decoding `\xNN`/`\NNN`
* identical code at `sys/netgraph7/netgraph/ng_parse.c:1764,1813`

Both prototypes declare `__restrict` (`sys/sys/libkern.h:131`), so per C11
§6.7.3.1 this is undefined behavior. It is *correct today* only because the
in-tree `sys/libkern/strcpy.c:40` is a forward byte loop
(`for (; (*to = *from) != 0; ++from, ++to);`), which shifts left safely.

## Evidence (see run.log)

1. **`rig_intree`** — runs the verbatim `ng_get_string_token()` algorithm
   (`ng_parse.c:1548-1625`) with the verbatim in-tree strcpy, instrumented
   only to count range-intersecting invocations: parse output correct,
   **5 overlapping strcpy invocations** in a single parse of
   `"A\tB\x41C\"D\\E"`. The restrict violation is real and exercised on
   every escape sequence parsed.
2. **`rig_libc`** — negative control: with libc/builtin strcpy the parse is
   byte-identical *on this guest* (DragonFly's libc strcpy is also
   byte-forward). The hazard is not with today's libc.
3. **`rig_block`** — a model of a vectorized/block-store strcpy (16-byte
   block loads and stores; the final partial block is stored whole — i.e.
   bytes past the NUL are written, the documented behavior of SSE/AVX
   implementations) applied to the same parser with the *exact kernel
   allocation size* (`kmalloc(strlen(s + start))`, note the missing `+1` at
   `ng_parse.c:1559`) placed at the end of a page followed by a `PROT_NONE`
   guard page: the forked child **dies from SIGSEGV** — the past-the-NUL
   store is an out-of-bounds write past the exact-size allocation
   (kernel-equivalent: write past a `kmalloc` object into the adjacent heap).
4. **`rig_fixed`** — the `fix.diff` treatment (the two overlap sites
   switched to `memmove`) produces **byte-identical** parser output.
5. **`wrestrict_shim.c`** — gcc 8.3 `-Wrestrict` compiles the two call
   sites *silently*: the compiler does not diagnose this pattern, so there
   is no build-time guard against the fragility.

## Why Info and not higher

* The parser input arrives through netgraph control messages, gated behind
  `caps_priv_check(SYSCAP_RESTRICTEDROOT)` at `sys/netgraph/socket/ng_socket.c:171-175`
  — root-only on DragonFly. No unprivileged-reachable bug exists in the
  shipped kernel.
* The in-tree strcpy and DFly libc strcpy are both byte-forward, so the
  shipped binaries behave correctly. The finding is latent fragility: any
  future swap of `strcpy` for an optimized/builtin implementation (the
  in-tree precedent is `sys/dev/raid/hptrr/osm.h:168`
  `#define strcpy(dst, src) __builtin_strcpy((dst), (src))`) silently
  becomes either wrong parsing or a heap overwrite.

## Adjacent observations (not findings)

* `ng_parse.c`'s hex/octal escape loops declare `k < 2` / `k < 3` but never
  increment `k` (`ng_parse.c:1601-1616` netgraph, `1773-1788` netgraph7), so
  `\x` consumes **all** consecutive hex digits and truncates to one byte.
  Bounded by the closing quote; harmless memory-safety-wise; FreeBSD-
  inherited; the rig's expected string encodes this real behavior.
* `ng_parse.c:1559` allocates `strlen(s + start)` with no `+1`; the copied
  string is one byte shorter than that (the leading quote), so it is an
  exact fit — but it leaves zero slack for any strcpy variant that writes
  past the NUL (demonstrated by `rig_block`).

## Fix validation

`fix.diff` (netgraph + netgraph7, `strcpy` -> `memmove` at the four overlap
sites) was validated at the **algorithm level** by `rig_fixed`
(byte-identical output). A full patched-kernel build/reboot was not
performed: the finding is Info/hardening with no kernel-reachable bug to
baseline, so `fix_status` is recorded as `not_testable` at the kernel level
with the rig-level validation as `fix_evidence`.

## Bottom line

`sys/libkern/strcpy.c` is a correct unbounded strcpy; the pass-2 value here
is the census that pinned down its one live contract violation — the
netgraph parser's overlapping use — and proved with a guard-page rig that
the byte-forward implementation is load-bearing. Apply `fix.diff` to make
the parser independent of strcpy implementation details.

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