β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3552

ng_xxx_newhook bookkeeping defects: free-slot search missing break makes every DLCI hook fail ENOBUFS; sizeof/strlen off-by-one rejects single-digit names; parsed dlci never stored; debughook stale after disconnect

Summary

Four independent template defects: (1) the free-channel loop only ever 'continue's, so chan always exits at XXX_NUM_DLCIS and every dlciNN hook creation returns ENOBUFS on a fresh node; (2) cp = name + sizeof(LEADIN) counts the NUL and skips the first digit ('dlci5' -> EINVAL, 'dlci55' parses as 5); (3) channel[chan].dlci is never assigned, killing dup detection and demux even with (1)+(2) fixed; (4) xxxp->debughook is never cleared in disconnect - latent stale pointer, never dereferenced. VERIFIED live: NGM_CONNECT returns ng_xxx_newhook's errno synchronously - dlci55 -> errno 55 (ENOBUFS), dlci5 -> errno 22 (EINVAL), dlci05 -> 55. Non-shipping template; no memory-safety impact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3552 Β· 9 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
v1demo.c β€” 8.2 KB view raw
run.sh β€” 190 B view raw
build.log β€” 3.1 KB view raw
run.log β€” 1.8 KB view raw
env.txt β€” 314 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 3.1 KB view raw

DF-3552 evidence pack β€” v1 ng_sample newhook bookkeeping defects

sys/netgraph/ng_sample.c ng_xxx_newhook()/ng_xxx_disconnect(): (1) free-slot search loop has no break β†’ every dlciNN hook fails ENOBUFS (:221-227); (2) sizeof vs strlen off-by-one skips the first digit of the hook name (:210) β†’ dlci5 = EINVAL, dlci55 parses as 5; (3) channel[chan].dlci never assigned (:228-232) β†’ dup detection and the demux search are dead; (4) debughook never cleared on disconnect (:238-241, :460-466) β€” latent stale pointer, never dereferenced.

Build / Run

Same v1demo.c, build.sh and module setup as the DF-3550 pack (one directory up). Decisive commands:

kldload ng_socket ; kldload /root/ngs1/ng_sample.ko
/root/v1demo hooktest dlci55    # expect: ENOBUFS (55) from missing break
/root/v1demo hooktest dlci5     # expect: EINVAL (22) from sizeof off-by-one

Expected

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

See run.log (from the same decisive session as DF-3550's).

VERDICT.md
↓ download raw

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_CONNECTs 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 continues β€” 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.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Four-part fix in fix.diff (strlen instead of sizeof; break-on-free-slot; store channel[chan].dlci; clear debughook on disconnect); kernel rebuild validation skipped per contract for Info-severity template defects.

fix.diff
↓ fix.diffper-fix-DF-3552

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (hooktest dlci55 -> errno=55 twice; hooktest dlci5 -> errno=22), v1demo.c (hooktest mode; v1 generic cmds return newhook's errno synchronously from sendto(2)), fix.diff (four-part template repair, git-apply --check clean)

PoC changes

Same v1demo.c driver as DF-3550 (hooktest mode); NGM_CONNECT initially waited for a response message, but v1 generic commands signal errors synchronously via sendto(2) and send no response - reworked to read the errno from sendto.

Verified recommended fix

Apply fix.diff: fix the slot-search break, the sizeof/strlen off-by-one, the missing channel dlci assignment, and clear debughook on disconnect.

Verdict

Reproduced live on the stock v1 netgraph stack: hooktest shows 'dlci5' -> EINVAL(22) and 'dlci55'/'dlci05' -> ENOBUFS(55) on a fresh sample node with all 16 channels free. Root causes in sys/netgraph/ng_sample.c: (1) the free-slot search loop (:221-227) has no break - it only ever 'continue's, so chan always exits at XXX_NUM_DLCIS and every DLCI hook creation fails ENOBUFS; (2) 'cp = name + sizeof(NG_XXX_HOOK_DLCI_LEADIN)' (:210) counts the NUL, skipping the first digit - single-digit names are impossible ('dlci5' -> isdigit('\0') -> EINVAL) and 'dlci55' parses as 5; (3) channel[chan].dlci is never assigned from the parsed value (:228-232), so duplicate-DLCI detection (:218-220) and the downstream demux search (:369-371) are dead even with (1)+(2) fixed; (4) xxxp->debughook is stored in newhook (:240) but never cleared in ng_xxx_disconnect (:460-466) - a latent stale pointer, never dereferenced in the file. Pure logic defects in demo/template code (module builds but does not ship; node creation root-gated) - no memory-safety impact.