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)
PoC verification
Evidence pack
findings/poc/DF-5848 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 amount -t nfs -o tcp 127.0.0.1:/exportloopback 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, stackm_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)
sys/netinet/tcp_output.c:896-922β every segment is allocatedMT_HEADER(m_gethdr/m_getl(..., MT_HEADER, M_PKTHDR, ...)at :898/:900/:942); iflen <= MHLEN - hdrlen - max_linkhdr(:909) the payload is copied INTO that mbuf β one MT_HEADER mbuf holds hdr+data.sys/net/if_loop.c:202+(if_simloop) hands the SAME mbuf to netisr;tcp_inputstrips IP/TCP headers viam_adjβ an MT_HEADER mbuf carrying pure data lands in the receiving socket buffer.soreceiveexplicitly acceptsMT_DATA || MT_HEADERas stream data (sys/kern/uipc_socket.c:1473,:1779) and returns the original mbufs to thesiocaller (:1842-1845).sys/vfs/nfs/nfs_socket.c:2614(nfsrv_rcvsoreceive into sio) βnfsrv_getstreamβ on completed recordnfs_realign(&ns_frag,...)at:2816.nfs_realign(:2256-2260) breaks on the first mbuf withm_len & 3or misalignedm_dataβ guaranteed by sending records whose size % 4 != 0 (or by pipelined records split mid-mbuf bym_copymat :2769) β thenm_dup_data(m, M_WAITOK)at :2267.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_pipeload (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
fixedStock 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
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.
No comments yet.