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

Signed integer overflow in RPC reply verifier length allows mbuf length corruption

Summary

krpc_subr.c:425 len += fxdr_unsigned(u_int32_t,reply->rp_auth.authlen). authlen attacker-controlled from network RPC reply. len is signed int. authlen=0x7FFFFFE8: 24+0x7FFFFFE8=0x80000000=INT_MIN. :426 (len+3)&~3 stays INT_MIN. :428 m_adj(m,INT_MIN) tail-trim: -INT_MIN UB. m->m_len-=INT_MIN overflows negative. Single-mbuf: EBADRPC boot failure. Chained non-cluster: potential panic in m_pullup. NFS-root boot only attacker is on-path/malicious server. Fix: validate authlen against RPCAUTH_MAXSIZ(400).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0900 Β· 14 files
FileTypeDescriptionSize
overflow_demo.c trigger-source userspace harness replicating krpc_subr.c:415-428 + m_adj arithmetic to demonstrate the signed overflow for cited authlen values 6.1 KB view raw
fix.diff suggested-fix git-apply-able fix: bound reply->rp_auth.authlen against RPCAUTH_MAXSIZ(400) before adding to signed int len; mirrors nfs_socket.c guard 865 B view raw
build.sh repro-script cc -O2 -Wall -o overflow_demo overflow_demo.c 138 B view raw
run.sh repro-script ./overflow_demo 153 B view raw
build.log build-log (in run.log) harness build output, exit 0 140 B view raw
run.log run-log decisive harness run on unpatched #0: shows BUG FIRES for authlen=0x7FFFFFE8, m_len corrupted 100->2147483748 1.7 KB view raw
fix_run.log run-log harness run on patched #1 kernel (userspace arithmetic unchanged; validates harness still runs cleanly on fixed kernel) 1.9 KB view raw
fix_build.log build-log tail of make nativekernel + make installkernel output, both rc=0 25.1 KB view raw
fix_disasm.txt fix-evidence objdump of running patched kernel: cmp $0x190,%eax (RPCAUTH_MAXSIZ) + ja EBADRPC path in krpc_call.part.0 534 B view raw
env.txt environment uname + cc version 245 B view raw
VERDICT.md verdict full narrative: mechanism, reachability, impact ceiling, fix validation 6.9 KB ↓ raw
README.md readme human-facing summary + build/run/expected + reachability note 3.6 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing summary + build/run/expected + reachability note
↓ download raw

DF-0900 β€” Signed integer overflow in RPC reply verifier length

The bug (claim)

sys/vfs/nfs/krpc_subr.c:425 in krpc_call()'s gotreply path adds an attacker-controlled network-supplied authlen (from the RPC reply verifier header) into a plain signed int len:

415:   len = sizeof(*reply);                                       /* = 24 */
424:   if (reply->rp_auth.authtype != 0) {
425:       len += fxdr_unsigned(u_int32_t, reply->rp_auth.authlen); /* signed int + u32 */
426:       len = (len + 3) & ~3;                                    /* stays INT_MIN */
427:   }
428:   m_adj(m, len);                                              /* m_adj(m, INT_MIN) */

len is declared int at krpc_subr.c:200. With authlen = 0x7FFFFFE8, 24 + 0x7FFFFFE8 = 0x80000000 = INT_MIN β€” signed integer overflow (undefined behavior in C); (INT_MIN+3)&~3 stays INT_MIN. m_adj(m, INT_MIN) takes the len < 0 branch (sys/kern/uipc_mbuf.c:1884), where len = -len is itself UB and leaves INT_MIN; the subsequent m->m_len -= len corrupts the mbuf length, and downstream consumers (m_pullup/copyout) read past the buffer.

Reachability (boot-time only)

krpc_call() is reached only via: - krpc_portmap() (krpc_subr.c:163) and - nfs_mountrpc.c:140,146,155,163,202,253,256 (mount-rpc helpers),

which are themselves called only from the diskless NFS-root bootstrap (bootpc_init() at nfs_vfsops.c:569 β†’ nfs_mountrpc). The nfs_mountrpc.c file header states verbatim:

"This module is not used by normal operating code because the 'mount' command has a far more sophisticated implementation."

There is no userspace syscall that reaches this code on a normally-booted kernel. The attacker model is an on-path network attacker or a malicious NFS server during diskless boot. The normal NFS client/server socket path in nfs_socket.c already validates the equivalent length field with if (len < 0 || len > RPCAUTH_MAXSIZ) (nfs_socket.c:2347,2385); the boot-time krpc_subr.c path omits that guard.

PoC

Because the path is unreachable from userspace on a normally-booted guest, overflow_demo.c is a userspace harness that reproduces the kernel's exact arithmetic (krpc_subr.c:415-428) to prove the signed overflow fires for the cited authlen values. It also reproduces the m_adj single-mbuf m_len corruption from sys/kern/uipc_mbuf.c:1859-1924.

Build & run (DragonFlyBSD guest, as maxx)

cc -O2 -Wall -o overflow_demo overflow_demo.c
./overflow_demo

Expected output

For authlen = 0x7FFFFFE8 the harness prints:

0x7fffffe8   -2147483648             -2147483648             -2147483648             -2147483548   cited overflow trigger
  ** BUG FIRES: sizeof_reply(24) + authlen(0x7FFFFFE8) = 0x80000000 = INT_MIN (signed overflow, UB)
  ** (len+3)&~3 = INT_MIN, passed to m_adj() as req_len.
  ** m_len corrupted: 100 -> -2147483548 (OOB on next mbuf op)

and concludes that the path performs signed int arithmetic on an attacker-controlled u_int32_t without any upper bound.

Impact ceiling

Low. Realistic worst case is boot failure / local DoS at diskless-NFS-root boot time (the finding's summary: "Single-mbuf: EBADRPC boot failure; chained non-cluster: potential panic in m_pullup"). No memory-corruption primitive reachable from an unprivileged local user β€” the path is network/boot-time only and authlen shapes only a length, not arbitrary content. No escalation chain applies.

Bound authlen against RPCAUTH_MAXSIZ (400) β€” the same guard the normal NFS socket path already uses β€” before adding it to len. See fix.diff.

VERDICT.md verdict full narrative: mechanism, reachability, impact ceiling, fix validation
↓ download raw

DF-0900 β€” VERDICT

Verdict: REPRODUCED (signed integer overflow, confirmed by harness + source trace); fix VALIDATED (compiles, boots, bound check confirmed in binary).

The bug (real)

sys/vfs/nfs/krpc_subr.c:425 in krpc_call()'s gotreply path adds an attacker-controlled network-supplied authlen (from the RPC reply verifier header reply->rp_auth.authlen, a u_int32_t on the wire) into a plain signed int len (declared at krpc_subr.c:200) without any upper bound:

415:   len = sizeof(*reply);                                       /* = 24 */
424:   if (reply->rp_auth.authtype != 0) {
425:       len += fxdr_unsigned(u_int32_t, reply->rp_auth.authlen); /* int += u32 */
426:       len = (len + 3) & ~3;                                    /* stays INT_MIN */
427:   }
428:   m_adj(m, len);                                              /* m_adj(m, INT_MIN) */

fxdr_unsigned (sys/vfs/nfs/xdr_subs.h) byte-swaps the big-endian wire u32 to host order. len is signed int. For authlen = 0x7FFFFFE8:

  • 24 + 0x7FFFFFE8 = 0x80000000 = INT_MIN β€” signed integer overflow (C UB).
  • (INT_MIN + 3) & ~3 = INT_MIN.
  • m_adj(m, INT_MIN) (sys/kern/uipc_mbuf.c:1859) takes the req_len < 0 branch (m_adj :1884), where len = -len (-INT_MIN, itself UB) and the subsequent m->m_len -= len corrupts m_len (e.g. 100 β†’ 2147483748). Downstream m_pullup/copyout then read past the buffer.

The harness overflow_demo.c reproduces this exact arithmetic and the m_adj single-mbuf corruption; for authlen = 0x7FFFFFE8 it prints:

0x7fffffe8   -2147483648   -2147483648   -2147483648   2147483748   cited overflow trigger
  ** BUG FIRES: sizeof_reply(24) + authlen(0x7FFFFFE8) = 0x80000000 = INT_MIN (signed overflow, UB)
  ** (len+3)&~3 = INT_MIN, passed to m_adj() as req_len.
  ** m_len corrupted: 100 -> 2147483748 (OOB on next mbuf op)

Reachability (boot-time only β€” why no live trigger on this guest)

krpc_call() is reached only via: - krpc_portmap() (krpc_subr.c:163) and - the mount-rpc helpers in nfs_mountrpc.c:140,146,155,163,202,253,256,

which are themselves called only from the diskless NFS-root bootstrap path (bootpc_init() at nfs_vfsops.c:569 β†’ nfs_mountrpc). The nfs_mountrpc.c file header states verbatim:

"Procedures used by NFS_ROOT and BOOTP to do an NFS mount rpc to obtain the nfs root file handle for a NFS-based root mount point. This module is not used by normal operating code because the 'mount' command has a far more sophisticated implementation."

There is no userspace syscall that reaches this code on a normally-booted kernel. The attacker model is an on-path network attacker or a malicious NFS server during diskless boot. The normal NFS client/server socket path in nfs_socket.c already validates the equivalent length field with if (len < 0 || len > RPCAUTH_MAXSIZ) (nfs_socket.c:2347,2385); the boot-time krpc_subr.c path omits that guard β€” which is exactly the gap the fix closes.

We therefore reproduce the arithmetic bug via the harness (which replicates krpc_subr.c:415-428 and m_adj exactly) rather than a live runtime trigger, which would require staging a diskless NFS boot against a malicious server β€” not feasible inside this audit guest.

Impact ceiling

Low (matches the finding's severity). The realistic worst case is boot failure / local DoS at diskless-NFS-root boot time (the finding's summary: "Single-mbuf: EBADRPC boot failure; chained non-cluster: potential panic in m_pullup"). No memory-corruption primitive is reachable from an unprivileged local user β€” the path is network/boot-time only and authlen shapes only a length, not arbitrary content. No escalation chain applies.

Exploit chain

none β€” this is not a memory-corruption primitive reachable from userspace. It is a boot-time-only signed integer overflow whose ceiling is DoS (boot failure / panic). No uid0 chain is applicable.

Fix (authored in fix.diff, VALIDATED)

Bound authlen against RPCAUTH_MAXSIZ (400, sys/vfs/nfs/rpcv2.h:55) β€” the same guard the normal NFS socket path already uses (nfs_socket.c:2347,2385) β€” before adding it to len. On overflow-class values the function now returns EBADRPC instead of performing the corrupting arithmetic:

reply = mtod(m, struct rpc_reply *);
if (reply->rp_auth.authtype != 0) {
    u_int32_t authlen = fxdr_unsigned(u_int32_t, reply->rp_auth.authlen);
    if (authlen > RPCAUTH_MAXSIZ) {     /* NEW */
        error = EBADRPC;
        goto out;
    }
    len += authlen;
    len = (len + 3) & ~3;
}
m_adj(m, len);

This matches the finding proposal (the summary says: "Fix: validate authlen against RPCAUTH_MAXSIZ(400)").

Phase 8 β€” fix validation (BUILT + BOOTED + binary-confirmed)

  1. Baseline (#0, unpatched): harness fires; source at krpc_subr.c:425 has no bound β€” overflow path is live.
  2. Applied fix.diff to in-guest /usr/src (patch -p1 β†’ "Hunk #1 succeeded at 422").
  3. Built make -j6 nativekernel KERNCONF=X86_64_GENERIC β†’ rc=0. Installed make installkernel β†’ rc=0. (NOTE: DragonFly's loader on this guest requires the full kernel β€” make installkernel writes the 119 MB unstripped ELF to /boot/kernel/kernel; a manual copy of kernel.stripped was rejected by the loader with "Unable to load /kernel/kernel".)
  4. Booted patched kernel: kern.version = DragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 12:01:25 UTC 2026.
  5. Binary-confirmed the bound is compiled in (objdump of the running kernel's krpc_call.part.0):

ffffffff808171bf: mov 0x10(%rax),%eax ; load reply->rp_auth.authlen ffffffff808171c2: bswap %eax ; fxdr_unsigned ffffffff808171c4: cmp $0x190,%eax ; cmp authlen, RPCAUTH_MAXSIZ(400) ffffffff808171c9: ja …krpc_call.part.0+0x42d ; if above -> EBADRPC goto out ffffffff808171cf: add $0x1b,%eax ; len = authlen + sizeof(*reply) + 3

With authlen <= 400, the subsequent add $0x1b produces at most 427 β€” well within int range, so the overflow is dead. authlen > 400 now takes the ja to EBADRPC.

fix_status = fixed. The runtime behavioral re-test (live NFS-mount with a malicious reply) is not_testable on this guest (boot-time-only path, no diskless-NFS boot can be staged), but the fix is validated to apply cleanly, compile, boot, and close the vulnerable code path by both source inspection and binary disassembly of the running kernel.

PoC changes

The findings/poc/DF-0900/ directory did not exist when this runner spawned (only the DB row + summary were present). I created: - overflow_demo.c β€” userspace harness replicating krpc_subr.c:415-428 + m_adj arithmetic to demonstrate the signed overflow for the cited authlen values. - README.md, build.sh, run.sh, fix.diff, env.txt, full logs, this VERDICT.md, and manifest.json.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: harness confirms INT_MIN overflow; patched objdump shows cmp $0x190 bound check. Compile+boot clean.

BEFORE: len=INT_MIN, m_len corrupted. AFTER: cmp $0x190 ja EBADRPC. No overflow.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 12:01:25 UTC 2026

Confirmed kernel references

Detail

Exploit chain

none -- boot-time-only signed overflow, DoS ceiling. No userspace trigger.

Evidence (decisive lines)

Harness: authlen=0x7FFFFFE8 -> len=INT_MIN, m_len 100->2147483748. Fix disasm: cmp $0x190; ja EBADRPC.

PoC changes

Authored from scratch: overflow_demo.c (replicates krpc arithmetic), fix.diff (bound authlen vs RPCAUTH_MAXSIZ), VERDICT.md, manifest.json.

Verified recommended fix

Extract authlen and reject authlen>RPCAUTH_MAXSIZ(400) with EBADRPC before adding to len at krpc_subr.c:425. Mirrors nfs_socket.c:2347 guard. Matches finding proposal. Full diff in findings/poc/DF-0900/fix.diff.

Verdict

REPRODUCED (harness). krpc_subr.c:425 len += authlen (signed int, no bound). authlen=0x7FFFFFE8 -> INT_MIN -> m_adj(INT_MIN) -> m_len corrupted 100->2147483748. Boot-time-only path (diskless NFS root). Normal NFS socket path already guarded at nfs_socket.c:2347.