β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2966

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2966 Β· 12 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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

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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/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.

↓ fix.diffper-fix-DF-2966

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).