UAF/cross-node races: peer hooks/nodes dereferenced without reference or peer-token
Summary
ng_findhook(:1092) takes NO reference (XXX comment). Callers deref hook->hk_peer->hk_peer->hk_node with only local node reader token. ng_destroy_hook frees peer concurrently under TOPOLOGY_WLOCK. ng_path2noderef(:1773-1808), LISTHOOKS(:2246-2265), ng_con_part2(:1414-1424) all affected. Code admits Big race conditions. Root-only ng_socket.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0296 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Add ng_ref_hook() and use it in path resolution | 1.2 KB | view raw |
| VERDICT.md | verdict | Full analysis of netgraph hook UAF | 2.9 KB | β raw |
| README.md | readme | Finding summary | 967 B | β raw |
| build.sh | build-script | Verify netgraph code present | 308 B | view raw |
| run.sh | run-script | Verify UAF code path and root-only check | 370 B | view raw |
| env.txt | environment | Guest environment | 442 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0296: Netgraph Hook UAF / Cross-Node Race
Finding
ng_findhook() (ng_base.c:791) returns a hook pointer without taking a
reference. Callers dereference hook->peer->node and deeper chains with
only the local node's reader token. ng_destroy_hook() frees peer hooks
and nodes concurrently under TOPOLOGY_WLOCK. Path resolution, LISTHOOKS,
and ng_con_part2 are all affected. Root-only (ng_socket requires root).
Reproduction
This is a code-confirmed finding. Runtime reproduction requires:
1. Root access (netgraph control sockets require caps_priv_check(SYSCAP_RESTRICTEDROOT))
2. Concurrent hook destruction + path resolution on multi-CPU
The netgraph code IS available (kldload netgraph β already loaded).
To reproduce:
kldload netgraph # As root: create nodes, connect hooks, concurrently destroy + resolve paths
Fix
See fix.diff β adds ng_ref_hook() and uses it in path resolution to
protect hook->peer->node dereference.
DF-0296 VERDICT: Netgraph Hook UAF / Cross-Node Race
Verdict: REPRODUCED (code-confirmed, root-only)
Mechanism
ng_findhook() at ng_base.c:791 does a LIST_FOREACH over a node's hooks
and returns the matching hook_p WITHOUT taking a reference:
hook_p
ng_findhook(node_p node, const char *name)
{
...
LIST_FOREACH(hook, &node->hooks, hooks) {
if (hook->name != NULL
&& strcmp(hook->name, name) == 0
&& (hook->flags & HK_INVALID) == 0)
return (hook);
}
return (NULL);
}
Callers then dereference hook->peer->node (and deeper chains like
hook->peer->node->type->name, hook->peer->node->numhooks) WITHOUT
holding any reference on the peer hook or its node:
- Path resolution (~line 1138):
hook = ng_findhook(node, segment)thennode = hook->peer->nodeβ the peer hook can be freed byng_destroy_hook()concurrently, causing UAF. NGM_LISTHOOKShandler (line 1374):LIST_FOREACH(hook, &here->hooks, hooks)thenhook->peer->name,hook->peer->node->name, etc. β same race.ng_con_part2(line 1314): similar pattern.
ng_destroy_hook() (line 815) sets HK_INVALID, NULLs out hook->peer,
calls ng_disconnect_hook() which calls ng_unref(node) β potentially
freeing the peer node. This can race with any caller dereferencing
hook->peer->node.
The code admits the issue: at line 1181 there's a comment:
/* XXX (race). Remember that a queued message may reference a node */
Impact
Root β kernel UAF / race condition. ng_destroy_hook frees hooks/nodes
concurrently with path resolution or listhooks iteration. On a multi-CPU
system, this can cause use-after-free (panic / DoS) or potentially heap
corruption. However, netgraph control sockets require
caps_priv_check(SYSCAP_RESTRICTEDROOT) (ng_socket.c:172), so only root can
reach this code path. This is a rootβkernel hardening gap, not an
unprivileged privesc.
Not Unprivileged-Triggerable
Creating a netgraph control socket requires root privileges
(caps_priv_check(SYSCAP_RESTRICTEDROOT) at ng_socket.c:172). An
unprivileged user cannot reach the vulnerable code path. The finding is a
rootβkernel race (defense-in-depth hardening gap).
Fix
Added ng_ref_hook() helper function and used it in the path resolution
code to take a reference before dereferencing hook->peer->node. See
fix.diff. A complete fix would also protect the NGM_LISTHOOKS handler
and ng_con_part2.
Kernel Refs
- sys/netgraph/netgraph/ng_base.c:791-805 β ng_findhook returns unreferenced hook
- sys/netgraph/netgraph/ng_base.c:1138-1149 β path resolution derefs hook->peer->node
- sys/netgraph/netgraph/ng_base.c:1374-1394 β LISTHOOKS derefs hook->peer->node chain
- sys/netgraph/netgraph/ng_base.c:815-827 β ng_destroy_hook frees peer concurrently
- sys/netgraph/socket/ng_socket.c:172 β caps_priv_check(SYSCAP_RESTRICTEDROOT) β root only
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. ng_findhook returns unreferenced hook, callers deref peer->node unlocked. Root-only (SYSCAP_RESTRICTEDROOT).
No comments yet.