DF-3204 / verdict.json
{ "finding_id": "DF-3204", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "The RPC_PROGMISMATCH exit of krpc_call leaks the reply mbuf chain, demonstrated on the guest through the verbatim production code: with an in-bounds 24-byte attacker reply carrying accept-status 2, 50/50 calls returned EBADRPC and netstat -m showed mbufs in use 7 -> 57, exactly one leaked mbuf per call, still allocated afterwards, while the honest-reply control loop stayed flat (57 -> 57); the DF-3203 coupling was also reproduced by planting 0x00000002 at mbuf offset 20..23 with a userland primer and sending a 16-byte short reply, which took the same leak exit on the uninitialized value 3/3 times with exactly +3 leaked mbufs. Root cause is exact: at krpc_subr.c:393-395 the PROGMISMATCH branch does goto out while m owns the chain taken from sio.sb_mb at :367, and the out: path at :437-441 frees only mhead and from and closes the socket. The real-world v2-only mountd fallback (md_mount tries MOUNT v3 then retries v1 on EBADRPC, nfs_mountrpc.c:140-163) drives the same exit once per boot RPC. Bounded impact: orphaned-but-allocated mbufs, no UAF or double free, rate-limited by the boot RPC pattern; not a local unpriv->root vector since krpc is boot-path only. The validated one-line fix (m_freem(m) before the goto out) removed the leak (+0 mbufs across 50 calls) while preserving EBADRPC semantics 50/50.", "exploit_chain": "boot-network attacker (DF-3205 delivery) replies PROGMISMATCH, or a v2-only mountd forces the md_mount v3->v1 fallback, or the DF-3203 stale rp_status read happens to hold 2; each occurrence orphans one soreceive-d reply mbuf chain (kernel mbuf pool consumption; bounded availability impact)", "evidence": "run.log: T3 marker 50/50 EBADRPC with mbufs before 7/146632 and after 57/146632, control T33 57 -> 57; run.couple.log: 3/3 EBADRPC with mbufs 7 -> 10 from primer-planted stale status 2; run.fixed.log: T3 50/50 EBADRPC with mbufs 8 -> 8 on the fixed kernel; pmis.log/good.log/fx_pmis.log/fx_good.log attacker-side views", "kernel_refs": ["sys/vfs/nfs/krpc_subr.c:391-396", "sys/vfs/nfs/krpc_subr.c:437-441", "sys/vfs/nfs/nfs_mountrpc.c:140-163"], "poc_changes": "seed sketch assumed an untestable boot lab; replaced with the established verbatim-interposition kernel-compiled rig driving the real UDP stack with a userland pmis-mode attacker; leak accounting via netstat -m first-line deltas bracketed around each sysctl-driven batch, with an honest-reply control batch as the differential", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Oct 20 17:57:48 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline decisive boot; fixed decisive boot #2: Tue Oct 20 18:25:39 UTC 2026)", "runtime_sec": 120, "guest_dirty": 0, "build_cmd": "sh build.sh (fixed variant: sh build.sh af834f6e30225b1af68cc83cbcdaf25e29e0e39ca6deaec6f3c13663a661b886)", "run_cmd": "sh run.sh ; fix A/B: sh run_fixed.sh", "code_hash": "7e1e3ce06a6846a1b942d9d9f0098be5685fe36980db2620828f691ffb429520 (dfrig13_krpc.c) ; d7337b8717b8d4edb990f51ff612ea099085970756a8314fbbe9ab328c0507f1 (attacker.c)", "notes": "impact dos reflects the mbuf pool consumption (bounded; a handful per boot in the realistic v2-fallback scenario); no UAF or double free exists on this path since the chain is orphaned while still allocated. Guest reset with-src performed after the run. Fixed source sha256 af834f6e30225b1af68cc83cbcdaf25e29e0e39ca6deaec6f3c13663a661b886 (fix_all.diff; the DF-3204 hunk is line-disjoint from the DF-3203 and DF-3205 hunks).", "recommended_fix": "m_freem(m) and NULL it before the EBADRPC goto out at krpc_subr.c:393-395 (validated in fix.diff)" } |