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

smbfs_advlock unconditionally overwrites lock type to SMB_LOCK_EXCL β€” shared locks become exclusive, unlocks acquire new locks

Summary

smbfs_vnops.c:927-939 inner switch sets lkop=SMB_LOCK_EXCL/SHARED/RELEASE per fl->l_type. :943 lkop=SMB_LOCK_EXCL UNCONDITIONALLY overwrites β€” inner switch dead code. :944 smbfs_smb_lock(np,lkop,...) always EXCL. F_RDLCK shared reads serialized exclusive. fcntl(F_SETLK,F_UNLCK) issues SMB_LOCK_EXCL not RELEASE = accumulates exclusive oplocks on server never released. Repeated calls exhaust server lock table = local-triggered remote DoS. Fix: remove stray :943 lkop=SMB_LOCK_EXCL.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0835 Β· 14 files
FileTypeDescriptionSize
trigger.c trigger-source fcntl(F_SETLK, F_RDLCK) trigger for smbfs files 2.8 KB view raw
build.sh build-script cc -o trigger trigger.c 171 B view raw
run.sh run-script ./trigger <file-on-smbfs> 553 B view raw
fix.diff suggested-fix remove unconditional lkop=SMB_LOCK_EXCL at line 943 387 B view raw
VERDICT.md verdict full analysis: mechanism, trace, before/after disassembly 6.9 KB ↓ raw
README.md readme summary + reproduction guide 1.9 KB ↓ raw
baseline_disasm.txt disassembly unpatched smbfs_advlock (bug: xor esi,esi = always EXCL) 5.9 KB view raw
patched_disasm.txt disassembly patched smbfs_advlock (switch live as jump table) 6.9 KB view raw
fix_build.log build-log patched smbfs.ko build output (exit 0) 1001 B view raw
fix_run.log run-log before/after disassembly comparison + module load test 1.8 KB view raw
run.log run-log trigger run (validates syscall surface) 68 B view raw
env.txt environment uname, cc version, module state 369 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
README.md readme summary + reproduction guide
↓ download raw

DF-0835 β€” smbfs_advlock unconditional lock-type overwrite

Summary

smbfs_advlock (sys/vfs/smbfs/smbfs_vnops.c:943) unconditionally sets lkop = SMB_LOCK_EXCL after an inner switch has already mapped the POSIX lock type (F_RDLCK→SHARED, F_WRLCK→EXCL, F_UNLCK→RELEASE). The overwrite makes the switch dead code: every F_SETLK advisory-lock request is sent to the SMB server as an exclusive lock, regardless of the requested type.

Impact

  • F_RDLCK (shared/read) β†’ sent as exclusive β†’ readers serialize (logic/auth violation)
  • F_UNLCK via F_SETLK β†’ sent as exclusive acquire (not release) β†’ server lock table exhaustion β†’ local-triggered remote DoS

Medium severity. No memory corruption, no escalation.

Reproduction

Full runtime proof requires an SMB server + mount_smbfs (root-only mount). The audit guest has no SMB server, so the primary evidence is the deterministic code trace + compiled disassembly showing the switch is dead-code-eliminated in the shipping smbfs.ko.

Build & run the trigger

cc -o trigger trigger.c
./trigger <file-on-smbfs-mount>

Without an smbfs mount, the trigger validates the fcntl syscall surface and documents the call path. See VERDICT.md for the full code trace and disassembly proof.

Files

File Description
trigger.c C trigger: fcntl(F_SETLK, F_RDLCK) on an smbfs file
fix.diff One-line fix: remove lkop = SMB_LOCK_EXCL at line 943
VERDICT.md Full analysis: mechanism, trace, before/after disassembly
baseline_disasm.txt Unpatched smbfs_advlock disassembly (bug present)
patched_disasm.txt Patched smbfs_advlock disassembly (bug fixed)
fix_build.log Patched smbfs.ko build output
fix_run.log Before/after disassembly comparison
env.txt Guest environment
manifest.json Machine-readable catalog
VERDICT.md verdict full analysis: mechanism, trace, before/after disassembly
↓ download raw

DF-0835 β€” smbfs_advlock unconditionally overwrites lock type to SMB_LOCK_EXCL

Verdict: REPRODUCED (logic/auth bug); FIX VALIDATED

Status: reproduced (deterministic code trace + compiled-code disassembly proof) Impact: logic/auth violation β€” shared (read) locks become exclusive; unlocks within F_SETLK acquire new exclusive locks. Local-triggered remote DoS ceiling (server lock table exhaustion from never-released exclusive oplocks). Severity: Medium (confirmed) Confidence: certain


Mechanism

smbfs_advlock (sys/vfs/smbfs/smbfs_vnops.c:882) is the VOP_ADVLOCK handler for smbfs vnodes. In the F_SETLK case (line 925), an inner switch (lines 927–939) correctly maps the POSIX lock type to the SMB lock operation:

sys/vfs/smbfs/smbfs_vnops.c:927-939 (the CORRECT mapping β€” dead code in baseline):
    switch (fl->l_type) {
        case F_WRLCK:  lkop = SMB_LOCK_EXCL;    break;   // exclusive
        case F_RDLCK:  lkop = SMB_LOCK_SHARED;  break;   // shared/read
        case F_UNLCK:  lkop = SMB_LOCK_RELEASE; break;   // unlock
        default:       return EINVAL;
    }

But then line 943 unconditionally overwrites the result:

sys/vfs/smbfs/smbfs_vnops.c:943 (THE BUG):
    lkop = SMB_LOCK_EXCL;   /* <-- unconditional overwrite, makes switch dead code */

Line 944 then calls smbfs_smb_lock(np, lkop, ...) with the overwritten value.

Downstream effect

smbfs_smb_lock (sys/vfs/smbfs/smbfs_smb.c:129) dispatches to smbfs_smb_lockandx (sys/vfs/smbfs/smbfs_smb.c:92), which builds the SMB_COM_LOCKING_ANDX request:

  • Line 101–102: if (op == SMB_LOCK_SHARED) ltype |= SMB_LOCKING_ANDX_SHARED_LOCK; β€” With op always SMB_LOCK_EXCL (0), the SHARED flag is never set β†’ the lock sent to the server is always exclusive.
  • Line 115: mb_put_uint16le(mbp, op == SMB_LOCK_RELEASE ? 1 : 0); (unlock count) β€” With op always 0, this is always 0 β†’ no unlocks are ever issued.
  • Line 116: mb_put_uint16le(mbp, op == SMB_LOCK_RELEASE ? 0 : 1); (lock count) β€” With op always 0, this is always 1 β†’ a new lock is always acquired.

Concrete consequences

  1. F_RDLCK (shared/read lock) β†’ sent as SMB_LOCK_EXCL (exclusive). A second reader on the same byte range is blocked β€” violating POSIX shared-lock semantics. Programs relying on concurrent read access via F_RDLCK on smbfs will serialize.
  2. F_UNLCK within F_SETLK β†’ sent as SMB_LOCK_EXCL (acquire exclusive, NOT release). Each "unlock" silently accumulates a new exclusive oplock on the server that is never released. Repeated lock/unlock cycles exhaust the server's lock table β†’ local-triggered remote DoS.

The case F_UNLCK: in the outer switch (line 950–952) correctly uses SMB_LOCK_RELEASE directly, so direct F_UNLCK advisory-lock calls (not via F_SETLK) are unaffected. The bug is specifically in the F_SETLK code path.


Reproduction evidence

Reachability

  • smbfs_advlock is wired as .vop_advlock in smbfs_vnode_vops (sys/vfs/smbfs/smbfs_vnops.c:93).
  • mount_smbfs exists at /sbin/mount_smbfs in the guest; the smbfs.ko module loads cleanly.
  • Mounting an smbfs share requires root (vfs.usermount=0); the post-mount fcntl(F_SETLK) call is unprivileged (any user with file access).
  • Full runtime protocol-level proof requires an SMB server (the audit guest has none). The deterministic code trace + compiled disassembly is the primary evidence, per the task's acceptability clause for hard-to-stage logic bugs.

Compiled-code proof (definitive)

The shipping /boot/kernel/smbfs.ko was disassembled. The compiler dead-code-eliminated the inner switch entirely because line 943 always overwrites lkop:

BASELINE (unpatched #0 kernel) β€” smbfs_advlock F_SETLK path:

; F_SETLK handler (at +256):
0xce20 <+256>: movzwl 0x14(%r12),%eax    ; load fl->l_type
0xce26 <+262>: sub    $0x1,%eax          ; range check only
0xce2d <+269>: ja     <EINVAL>           ; default β†’ EINVAL
; ... NO switch assignment β€” dead-coded ...
; lf_advlock call, then:
0xce5e <+318>: xor    %esi,%esi          ; op = 0 = SMB_LOCK_EXCL (HARDCODED!)
0xce60 <+320>: mov    %r15,%rdi          ; np
0xce63 <+323>: callq  smbfs_smb_lock     ; ALWAYS called with op=0

Contrast with the correct case F_UNLCK: path (outer switch):

0xce02 <+226>: mov    $0x2,%esi          ; op = 2 = SMB_LOCK_RELEASE (correct!)

Fix

One-line removal of the unconditional overwrite at smbfs_vnops.c:943.

--- a/sys/vfs/smbfs/smbfs_vnops.c
+++ b/sys/vfs/smbfs/smbfs_vnops.c
@@ -940,7 +940,6 @@
        error = lf_advlock(ap, &np->n_lockf, size);
        if (error)
            break;
-       lkop = SMB_LOCK_EXCL;
        error = smbfs_smb_lock(np, lkop, id, start, end, &scred);

After removal, the switch result (lkop) flows directly into smbfs_smb_lock.

Fix validation (compiled-code before/after)

The patched smbfs.ko was built from /usr/src/sys/vfs/smbfs/ with fix.diff applied (build exit 0, no warnings). Disassembly confirms the switch is now live β€” compiled as a jump table lookup:

PATCHED β€” smbfs_advlock F_SETLK path:

0xc5d0 <+256>: movzwl 0x14(%r14),%eax    ; load fl->l_type
0xc5d5 <+261>: sub    $0x1,%eax          ; index = l_type - 1
0xc5ef <+287>: mov    0x0(,%rax,4),%eax  ; lkop = jump_table[index]  <-- SWITCH LIVE!
0xc5f9 <+297>: mov    %eax,-0x4c(%rbp)   ; save lkop to stack
; ... lf_advlock call ...
0xc60c <+316>: mov    -0x4c(%rbp),%esi   ; op = lkop (from switch, NOT hardcoded!)
0xc621 <+337>: callq  smbfs_smb_lock

Jump table at .rodata+0x300 = [1, 2, 0]:

l_type constant index (l_typeβˆ’1) table value SMB op Correct?
1 F_RDLCK 0 1 SMB_LOCK_SHARED βœ“ shared/read
2 F_UNLCK 1 2 SMB_LOCK_RELEASE βœ“ unlock
3 F_WRLCK 2 0 SMB_LOCK_EXCL βœ“ exclusive

The patched module loads cleanly (kldload smbfs succeeds; kldstat confirms).


PoC changes

The finding's PoC folder (findings/poc/DF-0835/) was created from scratch (the orchestrator had not seeded it). Contents: - trigger.c β€” C program issuing fcntl(F_SETLK, F_RDLCK) on an smbfs file; documents the syscall surface (full runtime proof needs an SMB share). - fix.diff β€” the one-line removal (git-apply-able, validated). - baseline_disasm.txt β€” full unpatched smbfs_advlock disassembly. - patched_disasm.txt β€” full patched smbfs_advlock disassembly + jump table. - fix_build.log β€” patched module build output. - fix_run.log β€” before/after disassembly comparison.

Impact ceiling

  • Shared-lock semantics violation: F_RDLCK requests serialize readers (auth/logic).
  • Remote DoS: F_UNLCK-via-F_SETLK accumulates unreleased exclusive oplocks on the server; repeated calls exhaust the server lock table.
  • No memory corruption, no escalation path.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED the fix: built the patched smbfs.ko module (build exit 0, no warnings) from /usr/src/sys/vfs/smbfs/ with fix.diff applied. Before/after disassembly comparison is definitive: BASELINE compiles the F_SETLK inner switch as dead code ('xor %esi,%esi' -> op always 0=SMB_LOCK_EXCL); PATCHED compiles the switch as a live jump-table lookup ('mov 0x0(,%rax,4),%eax' -> 'mov -0x4c(%rbp),%esi' before the smbfs_smb_lock call). Jump table at .rodata+0x300=[1,2,0] correctly maps F_RDLCK->SHARED, F_UNLCK->RELEASE, F_WRLCK->EXCL. Patched module loads cleanly (kldload smbfs succeeds). Note: smbfs is a loadable module only (not in X86_64_GENERIC), so module-level validation is the correct fix unit -- a full kernel rebuild would not change the kernel binary.

BASELINE (unpatched): 0xce5e <+318>: xor %esi,%esi -> op=0=SMB_LOCK_EXCL ALWAYS (switch dead-coded) | PATCHED: 0xc5ef <+287>: mov 0x0(,%rax,4),%eax (switch table lookup) -> 0xc60c <+316>: mov -0x4c(%rbp),%esi (op from switch result) | Jump table .rodata+0x300 = 01000000 02000000 00000000 -> F_RDLCK=1->SHARED, F_UNLCK=2->RELEASE, F_WRLCK=3->EXCL | Module load: 'PATCHED smbfs.ko loaded successfully'
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 (smbfs.ko module rebuilt with fix.diff from /usr/src/sys/vfs/smbfs/; module loaded as kld in lieu of full kernel rebuild since smbfs is not compiled into GENERIC)

Confirmed kernel references

Detail

Exploit chain

none -- this is a lock-type logic/auth bug (not memory corruption). No escalation chain possible. Impact is limited to: (1) shared-lock semantics violation (readers serialized), (2) local-triggered remote DoS via server lock-table exhaustion from unreleased exclusive oplocks accumulated by F_UNLCK-via-F_SETLK.

Evidence (decisive lines)

BASELINE (unpatched #0 smbfs.ko) smbfs_advlock F_SETLK path: '0xce5e <+318>: xor %esi,%esi' -> op=0=SMB_LOCK_EXCL ALWAYS (inner switch dead-code-eliminated by compiler). Contrast F_UNLCK outer path: '0xce02 <+226>: mov $0x2,%esi' -> op=2=SMB_LOCK_RELEASE (correct). PATCHED smbfs.ko F_SETLK path: '0xc5ef <+287>: mov 0x0(,%rax,4),%eax' (jump table lookup) + '0xc60c <+316>: mov -0x4c(%rbp),%esi' (op from switch result). Jump table at .rodata+0x300 = [1,2,0]: F_RDLCK->SHARED(1), F_UNLCK->RELEASE(2), F_WRLCK->EXCL(0) -- all correct.

PoC changes

Created the entire PoC folder from scratch (orchestrator had not seeded it). trigger.c issues fcntl(F_SETLK, F_RDLCK) on an smbfs file (documents syscall surface; full runtime needs SMB server). fix.diff is the one-line removal of the unconditional overwrite at smbfs_vnops.c:943. baseline_disasm.txt and patched_disasm.txt capture the before/after compiled-code proof.

Verified recommended fix

Remove the unconditional 'lkop = SMB_LOCK_EXCL;' at sys/vfs/smbfs/smbfs_vnops.c:943 (one-line deletion). This allows the inner switch result to flow through to smbfs_smb_lock, correctly mapping F_RDLCK->SMB_LOCK_SHARED, F_WRLCK->SMB_LOCK_EXCL, F_UNLCK->SMB_LOCK_RELEASE. Matches the finding proposal (the DB summary also recommended removing the stray line 943). Full git-apply-able diff in findings/poc/DF-0835/fix.diff.

Verdict

REPRODUCED. smbfs_vnops.c:943 unconditionally sets lkop=SMB_LOCK_EXCL after the inner switch (lines 927-939) has correctly mapped F_RDLCK->SMB_LOCK_SHARED, F_WRLCK->SMB_LOCK_EXCL, F_UNLCK->SMB_LOCK_RELEASE. The overwrite makes the switch dead code: every F_SETLK advisory-lock request is sent to the SMB server as an EXCLUSIVE lock (SMB_COM_LOCKING_ANDX without SMB_LOCKING_ANDX_SHARED_LOCK, unlock-count=0/lock-count=1 even for F_UNLCK). Definitive proof from compiled disassembly of the shipping /boot/kernel/smbfs.ko: the compiler dead-code-eliminated the entire inner switch -- the F_SETLK path hardcodes op via 'xor %esi,%esi' (esi=0=SMB_LOCK_EXCL) before calling smbfs_smb_lock, while the correct F_UNLCK outer-switch path uses 'mov $0x2,%esi' (SMB_LOCK_RELEASE). Consequences: (1) F_RDLCK shared/read locks serialize readers (POSIX shared-lock semantics violation); (2) F_UNLCK within F_SETLK acquires a new exclusive oplock instead of releasing -> repeated lock/unlock cycles exhaust the server lock table (local-triggered remote DoS). Reachability confirmed: .vop_advlock wired at smbfs_vnops.c:93, mount_smbfs present at /sbin/mount_smbfs, smbfs.ko loads cleanly. Full runtime protocol-level proof needs an SMB server (not in guest); the deterministic code trace + disassembly is the primary evidence per task acceptability for logic bugs.