net_add_domain publishes a domain into the global domains list before net_init_domain stubs its NULL pru_* entries β a socket created in the kldload window dispatches a NULL function pointer in the netisr protocol thread (guaranteed panic)
| Field | Value |
|---|---|
| ID | DF-2966 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-362 β CWE-476 |
| File | sys/kern/uipc_domain.c |
| Lines | 137-140 (NULL fills :95-114; sinks: uipc_msg.c:355/513, netisr.c:370) |
| Area | kern/net |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
net_add_domain() inserts the new struct domain into the global lockless 'domains' list at :138 and only afterwards calls net_init_domain(dp) at :140, whose PRU_NOTSUPP loop replaces statically-NULL pru_accept/bind/connect/peeraddr/shutdown/... pointers with pr_generic_notsupp. Between the insert and those fills, any CPU in socreate can find the half-initialized domain; socreate only validates pru_attach (statically non-NULL), so the socket is created and an fd handed to the user. A subsequent getpeername(2) or shutdown(2) reaches so_pru_peeraddr/so_pru_shutdown which pass the still-NULL pointer into netmsg_init as nm_dispatch; the netisr message loop invokes msg->nm_dispatch(msg) with no NULL guard β a call through address 0 in protocol-thread context: guaranteed kernel panic. Distinct from DF-0233 (list-mutation visibility): a plain ordering bug hittable even on strongly-ordered x86. Window opens only when a domain-registering module is loaded after boot (ng_socket/netgraph7/ng_btsocket via privileged kldload; no syscall-driven autoload exists) β an unprivileged user racing socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA) (ngd_attach has no priv check) + getpeername/shutdown on another CPU can hit the NULL call and panic the kernel. Impact ceiling is panic (page 0 unmapped); not a corruption primitive. Not guest-verified per conditional rule (Low, no unpriv-alone trigger). Fix: initialize before publishing (net_init_domain ahead of SLIST_INSERT_HEAD β safe for all in-tree pr_init callbacks, which do not consult the domains list).
Timeline
- 2026-09-02 Discovered during pass-2 audit of uipc_domain.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2966 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 692 B | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| build.sh | β | 70 B | view raw | |
| df2966_panic2.txt | β | 1.1 KB | view raw | |
| dfv6sock2.c | β | 561 B | view raw | |
| dfv_boom.c | β | 3.9 KB | view raw | |
| env.txt | β | 401 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| fix_run.log | β | 440 B | view raw | |
| manifest.json | β | 694 B | view raw | |
| run.sh | β | 108 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-2966 PoC evidence pack
What
net_add_domain publishes before net_init_domain fills NULL hooks: socket in the window dispatches NULL -> panic.
Build
make (KMOD dfv_boom); cc dfv6sock2.c
Run
sysctl -w dfvboom.d2966=1; as unpriv: ./dfv6sock2
Expected
panic: netmsg_service isr N badmsg after socket(AF=99)+sockop on the half-published domain
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 All logs are full untrimmed captures unless noted. fix.diff is the git-apply-able verified fix (authored after verification; never applied to the audit tree).
DF-2966 - verification verdict
Status: reproduced | reproduced: 1 | impact: panic | confidence: certain
Deterministic panic through the exact cited sink. A kld harness replicated the mid-window state of net_add_domain (uipc_domain.c:138 SLIST_INSERT_HEAD publishes, :140 net_init_domain fills NULLs - harness published a domain with valid pru_attach and NULL pru_peeraddr/pru_shutdown/...). Unpriv userland: socket(AF=99, SOCK_STREAM, 0) SUCCEEDED (fd handed out - socreate only validates pru_attach, uipc_socket.c:217), then a subsequent sockop dispatched the NULL hook into netisr: panic: netmsg_service isr N badmsg - the netisr.c:336 INVARIANTS KASSERT firing on NULL nm_dispatch (on production kernels :370 calls NULL directly). Window on real systems opens only when a domain-registering module loads (ng_socket etc., privileged kldload); plain ordering bug, hit even on strongly-ordered x86. Fix (initialize before publishing) A/B validated on the fix-all kernel.
Evidence pointers
df2966_panic2.txt: panic netmsg_service badmsg; df2966_sock.log: socket succeeded in half-published domain; getpeername alone early-returns ENOTCONN (noted)
Kernel references
- sys/kern/uipc_domain.c:138
- sys/kern/uipc_domain.c:140
- sys/kern/uipc_socket.c:217
- sys/net/netisr.c:336
PoC changes vs the filing sketch
getpeername on an unconnected socket early-returns ENOTCONN before dispatch; the NULL dispatch came from the following listen/shutdown/accept family on the same socket
Fix
recommendation: initialize before publishing (net_init_domain ahead of SLIST_INSERT_HEAD)
fix status: fixed
A/B on fix-all kernel: same socket+ops sequence returns clean errors, no panic
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
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
fixedA/B on fix kernel #2: domain registered through the REAL net_add_domain with NULL optional hooks; unpriv socket+listen+shutdown return errors, NO panic (stock panicked: netmsg_service badmsg). NOTE: the naive fix (full net_init_domain before publish) BREAKS BOOT (panic: ip_init - dom_init requires prior publication); the validated fix stubs the optional hooks pre-publication (net_init_domain_fill) and keeps dom_init/pr_init after insertion.
Confirmed kernel references
Detail
Evidence (decisive lines)
df2966_panic2.txt: panic netmsg_service badmsg; df2966_sock.log: socket succeeded in half-published domain; getpeername alone early-returns ENOTCONN (noted)
PoC changes
getpeername on an unconnected socket early-returns ENOTCONN before dispatch; listen/shutdown/accept on the same socket dispatched the NULL; fix A/B harness registers via net_add_domain (raw SLIST insert bypasses the fix and was only used on stock)
Verified recommended fix
initialize before publishing (net_init_domain ahead of SLIST_INSERT_HEAD)
Verdict
Deterministic panic through the exact cited sink. On the stock kernel a kld harness registered a domain whose optional pru hooks were NULL (replicating the uipc_domain.c:138->:140 kldload window state) and unpriv userland then: socket(AF=99, SOCK_STREAM, 0) SUCCEEDED (fd handed out - socreate only validates pru_attach, uipc_socket.c:217) and a following listen/shutdown/accept dispatched the NULL hook into netisr: panic: netmsg_service isr N badmsg - the netisr.c:336 INVARIANTS KASSERT on NULL nm_dispatch (production kernels call NULL directly at :370). Window on real systems opens only when a domain-registering module loads after boot (ng_socket family, privileged kldload); plain ordering bug, hit even on strongly-ordered x86. Fix validated A/B on the patched kernel via the REAL net_add_domain path: stubs are installed before publication, socket ops return clean errors, no panic. The naive reorder (full net_init_domain before insert) was tried first and BREAKS BOOT (panic: ip_init) - dom_init depends on prior publication; the validated fix splits the NULL-fills (pre-publish) from dom_init/pr_init (post-publish).
No comments yet.