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

kernel nfsd panics under pipelined TCP load - m_dup_data MT_DATA assert via nfs_realign nfsrv_rcv

  • File: sys/kern/uipc_mbuf.c:1806 (via nfs_realign/nfsrv_rcv)
  • Severity: Low (remote-triggered kernel panic on nfsd; needs TCP client load)
  • CVSS: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
  • CWE: CWE-617 Reachable Assertion
  • Confidence: likely (panic captured live; root-cause pass pending)
  • Found in pass 2 (GLM 5.3) β€” incidental capture during the DF-3072 verification batch

Summary

Pipelined SunRPC GETATTR bursts over TCP against the stock kernel nfsd panic the guest: assertion "m->m_type == MT_DATA" failed in m_dup_data at uipc_mbuf.c:1806, reached via nfs_realign/nfsrv_rcv. Evidence transcript in findings/poc/DF-5848/incidental_nfsd_panic.txt (from the DF-3072 pack).

Status

new β€” needs dedicated root-cause + fix pass.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5848 Β· 7 files
FileTypeDescriptionSize
df5848_pipe.c β€” 4.9 KB view raw
incidental_nfsd_panic.txt β€” 32.6 KB view raw
panic-run1.txt β€” 617 B view raw
run.log β€” 1.7 KB view raw
fix.diff β€” 983 B view raw
env.txt β€” 1.3 KB view raw
VERDICT.md β€” 4.3 KB ↓ raw
VERDICT.md
↓ download raw

DF-5848 β€” VERDICT

Status: REPRODUCED (deterministic kernel panic, seconds, kernel nfsd on loopback TCP; original capture on stock #0 from the DF-3072 run, fresh capture this run). Impact: remote/local DoS (panic) of INVARIANTS kernels via nfsd; no memory corruption, no escalation.

1. Reproduction

  • Original (stock kernel #0: Thu Jul 2 06:02:54 UTC 2026, from the DF-3072 verification batch): pipelined kernel-NFS-client GETATTR/fstat load over a mount -t nfs -o tcp 127.0.0.1:/export loopback mount panicked after ~10 min β€” incidental_nfsd_panic.txt: panic: assertion "m->m_type == MT_DATA" failed in m_dup_data at /usr/src/sys/kern/uipc_mbuf.c:1806, stack m_dup_data ← nfs_realign.isra.3 ← nfsrv_rcv ← sys_nfssvc ← syscall2.
  • This run (fresh, deterministic): df5848_pipe.c β€” a raw SunRPC/TCP client to 127.0.0.1:2049 (no mount, no valid RPC, no auth) streaming TCP_NODELAY LASTFRAG records of 142-149 bytes plus 16-record pipelined bursts. Panic in under 90 s (panic-run1.txt), stack identical. Booted kernel at capture time was the DF-5847 diagnostic build (#1 Oct 26) β€” its only delta vs stock is nd6.c (inert for the mbuf/nfs path; md5-verified equal for uipc_mbuf.c and nfs_socket.c: 179f41cf…/84b1b11d… are the FIXED copies, stock equals the tree at 0c8009550937a3fd4597a1b9225543ef/84b1b11d8894dff5f44b90337aa707ff β€” nfs_socket.c untouched, uipc_mbuf.c stock at capture).

2. Root cause (line-accurate; each step verified in source and the

final step empirically)

  1. sys/netinet/tcp_output.c:896-922 β€” every segment is allocated MT_HEADER (m_gethdr/m_getl(..., MT_HEADER, M_PKTHDR, ...) at :898/:900/:942); if len <= MHLEN - hdrlen - max_linkhdr (:909) the payload is copied INTO that mbuf β€” one MT_HEADER mbuf holds hdr+data.
  2. sys/net/if_loop.c:202+ (if_simloop) hands the SAME mbuf to netisr; tcp_input strips IP/TCP headers via m_adj β€” an MT_HEADER mbuf carrying pure data lands in the receiving socket buffer. soreceive explicitly accepts MT_DATA || MT_HEADER as stream data (sys/kern/uipc_socket.c:1473, :1779) and returns the original mbufs to the sio caller (:1842-1845).
  3. sys/vfs/nfs/nfs_socket.c:2614 (nfsrv_rcv soreceive into sio) β†’ nfsrv_getstream β†’ on completed record nfs_realign(&ns_frag,...) at :2816. nfs_realign (:2256-2260) breaks on the first mbuf with m_len & 3 or misaligned m_data β€” guaranteed by sending records whose size % 4 != 0 (or by pipelined records split mid-mbuf by m_copym at :2769) β€” then m_dup_data(m, M_WAITOK) at :2267.
  4. sys/kern/uipc_mbuf.c:1806: KKASSERT(m->m_type == MT_DATA) fails on the loopback MT_HEADER data mbuf β†’ panic.

Why the original capture needed ~10 min of NFS-client luck while this trigger is deterministic: GETATTR requests are 4-byte-aligned XDR, so misalignment only arose from incidental record-mark straddling; sending deliberately misaligned record sizes hits the path immediately.

3. Fix and A/B validation

fix.diff relaxes the invariant to MT_DATA || MT_HEADER in m_dup_data β€” matching soreceive's own contract for stream data. The copy loop reads only the data region and always produces MT_DATA mbufs, so the output contract ("non-packet data chain for nfs_realign") is unchanged; MT_SONAME/MT_CONTROL mbufs remain rejected.

  • Validation kernel: tri-fixed build (#1 Mon Oct 26, INVARIANTS, -Werror, RC=0): uipc_mbuf.c fix + DF-5846 + DF-5847 fixes (disjoint subsystems).
  • A/B: identical df5848_pipe load (90 s+ budget, same nfsd setup) on the fixed kernel β€” no panic, nfsd up, connection serviced (run-fixed.log). Repeated for a longer 300 s run alongside the other two triggers' budgets.

4. Impact ceiling / privesc assessment

The assertion is a guard, not corruption: tripping it panics an INVARIANTS kernel (all this project's kernels) β€” DoS. Without INVARIANTS the copy itself would be harmless (it ignores m_type). There is no write primitive, no info leak, no lifetime issue β€” no path to uid=0. Reachability honestly stated: any local user can hit a loopback-serving nfsd (localhost mounts, common in testing/jails); a network-facing nfsd is hittable by any remote host that can reach 2049. Filed Low β€” consistent with a panic-only, service-load-gated trigger.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Stock baseline reproduced fresh (panic in seconds, stack identical to the original DF-3072 capture). Patched kernel: the exact client and load ran the full 300 s budget - 134493 individually-segmented misaligned records + 268976 pipelined burst records, 133968 reply reads (nfsd alive and servicing throughout), zero panics or assertions. Clean A/B.

run.log (stock panic transcript + full fixed-run client output); fix_build.log (clean -Werror build); fix.diff
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Mon Oct 26 06:04:19 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (tri-fixed: DF-5846+DF-5847+DF-5848)

Confirmed kernel references

Detail

Exploit chain

none - reachable-assertion class; INVARIANTS kernels panic; no corruption follows even without the assert (the copy ignores m_type)

Evidence (decisive lines)

panic-run1.txt (fresh panic, m_dup_data <- nfs_realign <- nfsrv_rcv <- sys_nfssvc); incidental_nfsd_panic.txt (original stock #0 capture from the DF-3072 batch); run.log (fixed kernel: 134493 single + 268976 burst records over 300 s, 133968 reply reads, zero panics); fix.diff

PoC changes

original evidence was an incidental capture under kernel-NFS-client GETATTR load (DF-3072); replaced with a purpose-built deterministic trigger: raw SunRPC/TCP record stream to 127.0.0.1:2049, TCP_NODELAY single-segment records sized 142-149 bytes (m_len%4 != 0 after header strip) plus 16-record pipelined bursts; reserved source port bound because vfs.nfs.reserved_port_only

Verified recommended fix

relax the m_dup_data invariant to accept MT_DATA or MT_HEADER, matching the soreceive stream-data contract (see fix.diff)

Verdict

Deterministic reproduction and full root cause. tcp_output builds every segment in an MT_HEADER mbuf (tcp_output.c:896-942, inline payload when it fits MHLEN); over loopback if_simloop delivers that same mbuf to the receiver, so soreceive (which explicitly blesses MT_HEADER stream data at uipc_socket.c:1473/1779 and returns original mbufs at :1842-1845) hands MT_HEADER data mbufs to nfsrv_rcv; a SunRPC record whose size is not 4-aligned (or one split mid-mbuf by nfsrv_getstream m_copym) trips nfs_realign (nfs_socket.c:2247-2270 via :2816) which calls m_dup_data, whose KKASSERT(m->m_type == MT_DATA) at uipc_mbuf.c:1806 panics. Fresh stock-path capture in seconds with a raw TCP client (no mount, no auth, no valid RPC - realign runs before RPC parsing); stack identical to the DF-3072 original. Fix (accept MT_DATA or MT_HEADER, matching the soreceive contract; the copy reads only the data region and always emits MT_DATA) built under -Werror INVARIANTS and the identical load survived a full 300 s with 400k+ records serviced, zero panics. Panic-only impact (the assert IS the guard); no memory-safety consequence, no escalation.