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)
PoC verification
Evidence pack
findings/poc/DF-0835 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 |
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;β WithopalwaysSMB_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) β Withopalways 0, this is always 0 β no unlocks are ever issued. - Line 116:
mb_put_uint16le(mbp, op == SMB_LOCK_RELEASE ? 0 : 1);(lock count) β Withopalways 0, this is always 1 β a new lock is always acquired.
Concrete consequences
- 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. - 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_advlockis wired as.vop_advlockinsmbfs_vnode_vops(sys/vfs/smbfs/smbfs_vnops.c:93).mount_smbfsexists at/sbin/mount_smbfsin the guest; thesmbfs.komodule loads cleanly.- Mounting an smbfs share requires root (
vfs.usermount=0); the post-mountfcntl(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
fixedVALIDATED 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'
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.
No comments yet.