# DF-3534 — netgraph7 ng_tty: ngt_shutdown() never drops the constructor
#          node reference — every ng_tty node ever created leaks

## What

In netgraph7 the node-teardown contract (see ng_rmnode,
sys/netgraph7/netgraph/ng_base.c:686-751) is: when a node type supplies a
`shutdown` method, `ng_rmnode()` drops only its own extra reference; **the
constructor (creation-time) reference must be dropped by the shutdown
method itself**.  ng_pred1 in the same tree documents the idiom
explicitly:

    sys/netgraph7/ng_pred1.c:349-351
        kfree(priv, M_NETGRAPH_PRED1);
        NG_NODE_SET_PRIVATE(node, NULL);
        NG_NODE_UNREF(node);		/* Let the node escape. */

ng7 `ngt_shutdown()` (sys/netgraph7/tty/ng_tty.c:553-571) frees `sc` but
never calls `NG_NODE_UNREF(node)` (and never clears the private pointer)
— so after `ngt_close()` tears the line discipline down, the node's
refcount stops at 1 and the `ng_node` is never freed:

    leak = one ng_node (~176 B, M_NETGRAPH_NODE) + its ID-hash entry
           per netgraph-tty ldisc install/removal, forever.

The v1 twin got this right (sys/netgraph/tty/ng_tty.c:554 does
`ng_unref(sc->node)`); the ng7 port dropped the call.

## Trigger (root: ngt_open requires SYSCAP_NONET_NETGRAPH)

Bare minimum — no hook, no data, no timer needed:

    posix_openpt(); grantpt(); unlockpt();
    slave = open(ptsname(master));
    ioctl(slave, TIOCSETD, &(int){6});    /* NETGRAPHDISC  -> ngt_open  */
    ioctl(slave, TIOCSETD, &(int){0});    /* back to termios -> ngt_close */
    close(slave); close(master);          /* -> one ng_node leaked */

Repeatable without bound; the accumulating nodes permanently pin the
ng_tty module (kldunload: Device busy), squat the node ID/name hashes and
— as DF-3513/DF-3514 already established — push `ng_nodes` across the
ng7 hash-rehash panic threshold (on stock module builds) turning routine
ldisc churn into `panic: hashdestroy: hash not empty`.

## Isolation proof (isolation.log, tty_leak2.c)

On a kernel/module with the DF-3531 ng_uncallout item leak already fixed
(so items are flat and cannot mask anything):

    nodd 500      (ldisc on/off only)   nodes 3 -> 503   (+1/cycle)
    nodata 500    (+ socket connect)    503 -> 1003      (+1/cycle)
    socksock 500  (socket<->socket, no tty) 1003 -> 1003 (FLAT)
    full 500      (+ data)              1003 -> 1.47K    (+1/cycle)

=> the leak is the tty ldisc lifecycle alone; the generic connect/data
path is clean; the chand callout is irrelevant (nodd leaks with no timer
ever armed).

## Fix (fix.diff — validated)

Add `NG_NODE_SET_PRIVATE(node, NULL); NG_NODE_UNREF(node);` at the end of
ngt_shutdown (mirroring ng_pred1.c:350-351).  Validated on the guest
(fix_validation.log): nodd 500 and 2x2000 stall-close cycles all leave
`netgraph_node` in-use flat at 2 (was +1/cycle); netgraph_hook stays 0.

## Build / Run

    cc -O2 -o tty_leak2 tty_leak2.c
    vmstat -m | grep netgraph_node
    ./tty_leak2 nodd 500
    vmstat -m | grep netgraph_node      # stock: +500
