# DF-3367 — RTM_CHANGE with gateway == destination permanently leaks the rtentry and desynchronizes the per-CPU routing tables

## Root cause (all in sys/net/route.c)

`route_output_change_callback` (rtsock.c:908-972) calls
`rt_setgate(rt, rt_key(rt), gateway)` for RTM_CHANGE. For a host-gateway
route whose new gateway equals its own destination, `rt_setgate` hits the
EADDRNOTAVAIL path (route.c:1193-1205): it **deletes the route from the
current CPU's tree** via a nested `rtrequest(RTM_DELETE, ...)` and returns
an error.

Refcount trace at that moment (rtsearch_msghandler route.c:1726-1729):

* callback entry: `rt->rt_refcnt++` → 1
* nested RTM_DELETE (route.c:824 `++rt->rt_refcnt` → 2; route.c:866 `rtfree`
  → 1; route removed from tree, `RTF_UP` cleared, `rt_key`/`rt_ifa` intact)
* callback returns error → `rt->rt_refcnt--` → **0**

Nobody holds a pointer anymore: the rtentry, its key chunk, and **its ifa
reference are never released** — a permanent leak on that CPU. Because
`rtsearch_msghandler` replies (and stops) on the first CPU error
(route.c:1751), CPUs 1..N-1 never run the change and keep the route: the
per-CPU tables diverge. A later `RTM_ADD` of the same destination then
succeeds on CPU0 and fails `EEXIST` on CPU1 → guaranteed
`panic("rtrequest1_msghandler: rtrequest table req ...")` (route.c:763-768).
(The panic leg was traced but deliberately not triggered during
verification; the leak and divergence legs were verified empirically.)

## Threat

Trigger requires routing-socket write privilege (root), so this is a
robustness/lifetime bug, not an escalation: a privileged (or compromised
routing daemon, e.g. via `route(8)`) process can permanently consume kernel
memory and panic the system.

## Verified on the guest (leakcheck.log / leakcheck2.log)

```
BEFORE
           routetbl   1.48K    257K       0    390M    3.16K
AFTER-200-CYCLES
           routetbl   3.82K    669K       0    390M    5.89K
routes visible on cpu0: 0
delete host 10.99.2.1
rc=0            <-- succeeded although cpu0 has no such route:
                    cpus 1..5 still do (divergence proven)
```

200 add+change cycles (+2340 in-use routetbl allocations, +412 KB); every
route is invisible on cpu0 yet still present on the other CPUs; cpu0's
copies are out-of-tree with refcount 0 — unfreeable.

## Fix sketch

In `rtsearch_msghandler`, drop the temporary reference via `rtfree()`-safe
logic (e.g. `RTFREE(rt)` after the callback — route.h:446 decrements and
finalizes at 0), or make `rt_setgate`'s EADDRNOTAVAIL path not mutate the
tree and instead return the error to the outer RTM_CHANGE, which should then
fail cleanly on every CPU.  Also revisit `rtredirect_oncpu` (route.c:425,
DF-0399) which reaches the same nested-delete with the return value ignored.
