# DF-3531 — netgraph7 ng_tty: closing a tty with a pending chand callout
#          permanently leaks the ng_item + node reference
#          (ng_tty twin of DF-3515/DF-3523, same ng_uncallout defect)

## What

ng7 ng_tty arms its output-drain timer with

    sys/netgraph7/tty/ng_tty.c:453-454 (ngt_start)
        if (!IF_QEMPTY(&sc->outq) && !callout_pending(&sc->chand))
            ng_callout(&sc->chand, sc->node, NULL, 1, ngt_timeout, NULL, 0);

`ng_callout()` (sys/netgraph7/netgraph/ng_base.c:3248-3268) stores a
`struct ng_item` — holding one `NG_NODE_REF` (ng_base.c:3257) — as the
callout argument.  Tearing the line discipline down runs

    sys/netgraph7/tty/ng_tty.c:252-253 (ngt_close)
        if (callout_pending(&sc->chand))
            ng_uncallout(&sc->chand, sc->node);

`ng_uncallout()` (ng_base.c:3273-3296) frees that item only inside
`if ((rval > 0) && ...)`.  On this kernel `callout_stop()` of an ARMED,
not-yet-fired callout completes the stop inline and returns 0 (the
DF-3515 `_callout_cancel_or_stop` contract violation, kern_timeout.c
early `return 0`), so the gate never passes and the item is orphaned.
Even when the gate passes, `NG_FREE_ITEM()` is flag-only in ng7
(netgraph.h:787-793) — `ng_free_item()` is never called.  Every tty
close with a pending chand callout permanently leaks:

* one `struct ng_item` (~141 B incl. objcache backing),
* one node reference — pinning the whole `ng_node` in the ID hash and
  pinning the module (`kldunload ng_tty` stays "Device busy").

## Trigger

Root (ngt_open requires SYSCAP_NONET_NETGRAPH, ng_tty.c:188): open a
pty, set ldisc NETGRAPHDISC(6), connect an ng7 data socket to
"ttyN:hook", queue more output than the pty drains (master unread), and
close the line discipline (fd close or TIOCSETD-away).  The drain timer
is then pending at ngt_close time and the cancel leaks.

Repeating this turns routine ng_tty use into unbounded permanent kernel
memory growth; the accumulating leaked refs also push the ng7 node
id/name hashes across the (separately filed, DF-3514) rehash panic
threshold — observed here: the very first 20-cycle smoke run without
the DF-3514 module fix wedged the guest in
`panic: hashdestroy: hash not empty`.

## Files

    ngtty_leak.c   PoC (leak mode + "drain" control mode)
    build.sh       build ng7 modules (with DF-3514 rehash fix only, so
                   the known DF-3514 module-build panic does not mask
                   the measurement) + compile the PoC
    run.sh         baseline -> leak -> control -> leak -> kldunload check
    fix.diff       two-layer fix validated here (kern_timeout.c contract
                   + ng_base.c item free), applied to the guest for the
                   fix-validation kernel/module

## Build (in-guest, root)

    cd /usr/src/sys/netgraph7/netgraph && make
    cd ../socket && make
    cd ../tty    && make
    kldload .../netgraph.ko .../ng_socket.ko .../ng_tty.ko
    cc -O2 -o ngtty_leak ngtty_leak.c

## Run (in-guest, root)

    vmstat -m | grep -E '^[[:space:]]+netgraph '   # baseline
    ./ngtty_leak 2000          # stall-close: LEAKS
    ./ngtty_leak 2000 drain    # drained control: ~flat
    kldunload ng_tty           # -> "Device busy"

## Observed (guest, DragonFly 6.5-DEVELOPMENT #0 INVARIANTS stock)

    baseline                          12 allocs   1.69K
    after 2000 stall-close cycles   1.96K allocs   283K   (+~1.9 allocs/cycle)
    after 2000 drain-close cycles   2.32K allocs   334K   (mostly flat; the
                                         residual is cycles whose drain
                                         outlasted the sleep windows and hit
                                         the same leak path)
    after 2000 more stall-cycles    3.98K allocs   573K   (monotone growth)
    kldunload ng_tty                "Device busy" (refs=0, pinned by leaks)

With BOTH fix layers installed (kern_timeout.c PREVENTED-reporting +
ng_base.c `ng_free_item`), the identical 2000-cycle stall-close run is
FLAT — see VERDICT.md and fix_run.log.

## IMPORTANT verification-stage discoveries (read VERDICT.md)

1. The baseline node-count growth is TWO stacked leaks: this one (items,
   via ng_uncallout) plus **DF-3534** — ng7 `ngt_shutdown` never drops the
   constructor node reference, so *every* ng_tty node leaks (even a bare
   TIOCSETD on/off cycle with no hook, no data, no timer).
2. The `ng_callout` oitem-free hunk from DF-3514/DF-3523's fix.diff is
   UNSAFE for ng_tty's 1-tick self-rearming timer (re-arm from inside the
   callback frees the in-flight fired item → double-free; reproduced).
   It is excluded from this pack's fix.diff.
3. The layer-1 kern_timeout fix, as validated, is not dispatch-precise:
   under close-vs-fire racing it reported PREVENTED for an already-
   dispatched callback and ng_uncallout freed an in-flight item
   (delayed `memory chunk already free` panic — panic.txt).
   fix_status = inconclusive for that reason; the leak itself is flat.
