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

Negative ioc_setupcnt bypasses signed upper-bound check β€” >131KB kernel stack OOB read exfiltrated to SMB server

Summary

smb_usr.c:299 if(dp->ioc_setupcnt>3) SIGNED comparison β€” negative values pass. :304 len=t2p->t2_setupcount=dp->ioc_setupcnt β€” implicit int->u_int16_t: -1 becomes 65535. :305 if(len>1) uses signed len=-1 false so t2_setupdata stays at internal t2_setup[2] (smb_rq.h:119 u_int16_t[2]=4 bytes). Downstream smb_rq.c:629-630 loop for(i=0;i<t2_setupcount(65535);i++) mb_put_uint16le(mbp,t2_setupdata[i]) reads 65535 u_int16_t from 4-byte stack array = ~131KB OOB read past kernel stack. Written into SMB TRANS2 request mbuf transmitted to connected server. Impact: kernel stack/heap info leak (KASLR bypass credentials keys) to attacker-controlled SMB listener + likely panic when read crosses unmapped page. Trigger: root /dev/nsmb0 SMBIOC_OPENSESSION to rogue server then SMBIOC_T2RQ with ioc_setupcnt=-1. Fix: if(dp->ioc_setupcnt<0||dp->ioc_setupcnt>3) return EINVAL.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0734 Β· 14 files
FileTypeDescriptionSize
trigger.c trigger-source libsmb-based trigger: establishes VC+share via SMBIOC_LOOKUP, then issues SMBIOC_T2RQ with ioc_setupcnt=-1 2.7 KB view raw
rogue_smb.c trigger-source Minimal SMB1/NetBIOS server handling NEGOTIATE/SESSION_SETUP/TREE_CONNECT 9.0 KB view raw
build.sh build-script Builds rogue_smb and trigger 213 B view raw
run.sh run-script Starts rogue server and runs trigger 300 B view raw
VERDICT.md verdict Full analysis with mechanism, panic trace, fix before/after 2.7 KB ↓ raw
panic.txt panic-signature vm_fault: fault on stack guard in smb_t2_request+0x2b7 called from smb_usr_t2request 792 B view raw
run.log run-log Baseline (panic) + patched (EINVAL) run output 1.2 KB view raw
fix_run.log run-log Patched module run β€” T2RQ returns EINVAL, no panic 395 B view raw
fix.diff suggested-fix git-apply-able fix: add dp->ioc_setupcnt < 0 guard at smb_usr.c:299 375 B view raw
fix_build.log build-log Full nativekernel build output 5.6 MB ↓ download
env.txt environment uname, cc version 247 B view raw
build.log build-log kernel build log excerpt proving -Werror clean compile of patched source 9 B view 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
VERDICT.md verdict Full analysis with mechanism, panic trace, fix before/after
↓ download raw

DF-0734 β€” Negative ioc_setupcnt bypasses signed upper-bound check β†’ >131KB kernel stack OOB read

Verdict: REPRODUCED (panic) + FIX VALIDATED

Bug mechanism

File: sys/netproto/smb/smb_usr.c:299 Bug: if (dp->ioc_setupcnt > 3) return EINVAL; β€” ioc_setupcnt is int, so a negative value (e.g., -1) passes this SIGNED upper-bound check (-1 > 3 is false).

Exploitation chain: 1. smb_usr.c:304 β€” len = t2p->t2_setupcount = dp->ioc_setupcnt; β€” t2_setupcount is u_int16_t; -1 (int) becomes 65535 (u_int16_t). 2. smb_usr.c:305 β€” if (len > 1) β€” len is int = -1; -1 > 1 is false; t2_setupdata stays pointing at the internal t2_setup[2] array (4-byte stack buffer in struct smb_t2rq). 3. smb_rq.c:629-630 β€” for (i = 0; i < t2p->t2_setupcount; i++) mb_put_uint16le(mbp, t2p->t2_setupdata[i]); β€” t2_setupcount = 65535 (u_int16_t), i is int; loop reads 65535 Γ— 2 = 131,070 bytes from a 4-byte stack array. 4. Result: ~131KB OOB read past the kernel stack. On this kernel, the read crosses the stack guard page β†’ panic: vm_fault: fault on stack guard in smb_t2_request() called from smb_usr_t2request().

Trigger setup

  • rogue_smb.c β€” Minimal SMB1/NetBIOS-over-TCP server (C). Handles NB session request β†’ SMB NEGOTIATE (CORE dialect) β†’ SESSION_SETUP_ANDX β†’ TREE_CONNECT_ANDX. Listens on 127.0.0.1:139.
  • trigger.c β€” Uses the libsmb API (same library as mount_smbfs) to establish a VC+share against the rogue server via SMBIOC_LOOKUP, then issues SMBIOC_T2RQ with ioc_setupcnt = -1.

Build & Run

# Build the rogue server
cc -o rogue_smb rogue_smb.c

# Build the trigger (requires libsmb)
cc -o trigger trigger.c -lsmb -I/usr/src/contrib/smbfs/include

# Run (must be root; load smbfs module first)
kldload smbfs
./rogue_smb 139 &    # start rogue SMB server
./trigger            # trigger the bug

Expected behavior

  • Unpatched kernel (#0): panic: vm_fault: fault on stack guard β€” the OOB read crosses the kernel stack guard page during TRANS2 request construction. Guest goes down.
  • Patched kernel/module: SMBIOC_T2RQ returns EINVAL (errno=22). No panic. Guest stays up.

Panic signature (baseline)

panic: vm_fault: fault on stack guard, addr: 0xfffff801187bc000
smb_t2_request() at smb_t2_request+0x2b7
smb_usr_t2request() at smb_usr_t2request+0xc6

Fix

smb_usr.c:299: Add dp->ioc_setupcnt < 0 || to the bounds check:

-   if (dp->ioc_setupcnt > 3)
+   if (dp->ioc_setupcnt < 0 || dp->ioc_setupcnt > 3)
        return EINVAL;

Since smb_usr.c is compiled into the smbfs.ko loadable module (not the base kernel), the fix can be validated by rebuilding just the module and hot-swapping it.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED the fix: trigger panics on the unpatched #0 baseline (vm_fault: fault on stack guard in smb_t2_request) and does NOT panic on the fixed smbfs.ko module (SMBIOC_T2RQ returns EINVAL=22, guest stays up). Fix closes the bug. Validated by rebuilding just the smbfs.ko module from patched source and hot-swapping it (kldunload/kldload), since smb_usr.c is compiled into the loadable module, not the base kernel.

baseline: panic: vm_fault: fault on stack guard, smb_t2_request+0x2b7 / patched: T2RQ returned rc=-1 errno=22 (EINVAL), guest stays up (confirmed twice)
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 (kernel unchanged; fix validated via smbfs.ko module hot-swap since smb_usr.c is in the module not the kernel)

Confirmed kernel references

Detail

Exploit chain

Info-leak class (root→kernel-stack OOB read). The trigger requires root (/dev/nsmb is mode 0700 root:wheel). The OOB read at smb_rq.c:629-630 copies kernel stack bytes into the TRANS2 request mbuf chain which is queued for transmission to the connected SMB server. On this kernel build the read immediately hits the stack guard page (panic) before data reaches the network, but the read primitive is real: with a different stack layout (no guard page or guard page further away), the ~131KB of kernel stack/heap content would be exfiltrated to an attacker-controlled SMB listener, enabling KASLR bypass and credential/key leakage. No uid0 escalation applies (trigger is root-only; this is a root→kernel info leak, relevant for jail escape / setuid binary exploitation / sandbox escape scenarios).

Evidence (decisive lines)

BASELINE (#0 kernel): panic: vm_fault: fault on stack guard, addr: 0xfffff801187bc000 / smb_t2_request() at smb_t2_request+0x2b7 / smb_usr_t2request() at smb_usr_t2request+0xc6 / Guest goes down. PATCHED (fixed smbfs.ko): T2RQ returned rc=-1 errno=22 (EINVAL) / Guest stays up / Run twice deterministically.

PoC changes

Authored the full evidence pack from scratch (no seeded PoC folder). rogue_smb.c: minimal C SMB1/NetBIOS server handling NEGOTIATE (CORE dialect), SESSION_SETUP_ANDX, TREE_CONNECT_ANDX. trigger.c: uses libsmb API (smb_ctx_init/resolve/lookup β€” same library as mount_smbfs) to establish VC+share against the rogue server, then issues SMBIOC_T2RQ with ioc_setupcnt=-1. Key discovery: must use SMBL_SHARE=2 (not 1) and pass argc/argv to smb_ctx_init so the UNC path is parsed and TREE_CONNECT is sent. fix.diff: adds dp->ioc_setupcnt < 0 || guard at smb_usr.c:299.

Verified recommended fix

At sys/netproto/smb/smb_usr.c:299, change if (dp->ioc_setupcnt > 3) to if (dp->ioc_setupcnt < 0 || dp->ioc_setupcnt > 3) so negative values are rejected with EINVAL before the signed/unsigned confusion at line 304 can store a wrapped u_int16_t into t2_setupcount. Matches the finding proposal exactly. Since smb_usr.c is compiled into the smbfs.ko loadable module, the fix can be hot-deployed without a kernel reboot.

Verdict

REPRODUCED. The signed/unsigned confusion at smb_usr.c:299 is real: if(dp->ioc_setupcnt > 3) is a SIGNED comparison so ioc_setupcnt=-1 passes. At smb_usr.c:304, t2_setupcount = dp->ioc_setupcnt stores -1 into a u_int16_t (=65535). At smb_usr.c:305, if(len > 1) uses the signed int len=-1 which is NOT > 1, so t2_setupdata stays at the internal 4-byte t2_setup[2] stack array. The downstream loop at smb_rq.c:629-630 for(i=0;i<65535;i++) mb_put_uint16le(mbp, t2_setupdata[i]) reads ~131KB past the kernel stack. On this kernel the read hits the stack guard page: panic: vm_fault: fault on stack guard in smb_t2_request+0x2b7 called from smb_usr_t2request+0xc6. Confirmed by stack trace matching the cited code path exactly. Fix validated: patched smbfs.ko module rejects ioc_setupcnt=-1 with EINVAL.