# DF-3552 — VERDICT: REPRODUCED (v1 ng_sample newhook bookkeeping defects)

**Impact:** none (hook-name parsing / slot allocation logic in a dormant
template) **Confidence:** certain

## What was run

`v1demo hooktest NAME` on the stock v1 netgraph stack: creates a `sample`
node (`NGM_MKPEER`, `socket(sd) <-> sample(downstream)`), then issues
`NGM_CONNECT`s trying to attach further hooks named `NAME` and `dlci05` on
the sample node. v1 generic commands return errors synchronously from
`sendto(2)`, so the observed errno is `ng_xxx_newhook()`'s own return.

## Observed (run.log)

```
=== hooktest dlci55:
CONNECT dlci55: FAILED errno=55 (No buffer space available)
CONNECT dlci05: FAILED errno=55 (No buffer space available)

=== hooktest dlci5:
CONNECT dlci5:   FAILED errno=22 (Invalid argument)
CONNECT dlci05:  FAILED errno=55 (No buffer space available)
```

## Why (kernel path, line-by-line)

Four independent defects in `ng_xxx_newhook()` / `ng_xxx_disconnect()`
(sys/netgraph/ng_sample.c):

1. **Missing `break` in the free-slot search — every DLCI hook fails with
   ENOBUFS.** (`:221-227`) After the "find existing dlci" loop misses, the
   free-slot loop `for (chan…) if (xxxp->channel[chan].dlci != -2)
   continue;` only ever `continue`s — it has no `break`, so `chan` always
   exits the loop at `XXX_NUM_DLCIS` and `return (ENOBUFS)` fires on a
   fresh node with all 16 channels free. Confirmed live: `dlci55` → 55.
2. **`sizeof` vs `strlen` off-by-one in the hook-name parse.** (`:210`)
   `cp = name + sizeof(NG_XXX_HOOK_DLCI_LEADIN)` — `sizeof("dlci")` counts
   the NUL, so `cp` skips the first digit character. `"dlci5"` leaves `cp`
   at the terminating NUL → `isdigit` fails → EINVAL (confirmed live:
   errno 22); `"dlci55"` parses as 5, not 55; single-digit DLCI hook names
   are impossible.
3. **`channel[chan].dlci` is never assigned.** (`:228-232`) The parsed
   `dlci` is used for the search but never stored, so even with (1) and (2)
   fixed, duplicate-DLCI detection (`:218-220`) never matches and the
   downstream demux search (`:369-371`) can never find a channel.
4. **Stale `debughook` pointer.** (`:238-241`, `:460-466`) `newhook` stores
   `xxxp->debughook = hook` for the debug hook but `ng_xxx_disconnect()`
   never clears it. Latent (never dereferenced anywhere in the file), so
   not separately exploitable — recorded for completeness since the field
   survives until the private struct is freed at rmnode.

Reachability: identical gating to DF-3550/DF-3551 — module builds from
/usr/src but does not ship; node creation requires root.

## Exploit chain

None — pure logic defects in demo/template code; no memory-safety impact.

## Fix validation

`fix.diff` fixes all four: `strlen` instead of `sizeof`, a real `break` in
the free-slot search, the missing `channel[chan].dlci = dlci` assignment,
and clearing `debughook` on disconnect. Kernel-rebuild validation skipped
per contract (Info severity).

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