--- Description --- An SMB2 SETINFO / FileRenameInformation with ReplaceIfExists = 1, whose target exists on disk only under a different case, returns STATUS_SUCCESS but creates a second directory entry instead of replacing the existing file. Both files remain on disk. Windows replaces the existing file in this situation. MS-FSA 2.1.5.15.11 specifies that the target lookup is case-insensitive. --- Affected versions --- Good: f0a4e897daa3a8002905fb5d161372d3b27cd42f (bb130f32^) Bad: bb130f32c9fdbb02a4e99d23953d090c3e992d0a "smbd: Rewrite rename_internals_fsp()", 2025-09-24 Both built from the same tree with identical configure options, tested on the same host, same filesystem, same smb.conf and same client. 4.24.3 is affected. The code path is unchanged in v4-25-stable and master, so those are very likely affected as well (verified by reading, not by testing). --- Reproducer --- Server: Ubuntu 22.04.5, ext4, upstream build, prefix /usr/local/samba configure --enable-debug --without-ad-dc --bundled-libraries=ALL smb.conf: [global] workgroup = WORKGROUP security = user server min protocol = SMB2_02 [reprocase] path = /srv/reprocase read only = No No VFS objects, no AD membership, no explicit "case sensitive". Client: Windows 11 21H2, calling MoveFileEx() with MOVEFILE_REPLACE_EXISTING: create dzdz.lnk (content "old") create {tmp}.tmp (content "new") MoveFileEx("{tmp}.tmp", "DZDZ.lnk", MOVEFILE_REPLACE_EXISTING) Result on bb130f32^ -- correct: 806385 -rwx--r-- 1 testsmb testsmb 5 dzdz.lnk Result on bb130f32 and on 4.24.3 -- two entries, call returned success: 806386 -rwx--r-- 1 testsmb testsmb 5 dzdz.lnk 806387 -rwx--r-- 1 testsmb testsmb 5 DZDZ.lnk --- Analysis --- bb130f32 introduces a divergence between the name used for the existence check and the name passed to the kernel. smb_fname_dst comes from filename_convert_dirfsp(), i.e. the case-insensitively resolved name. VALID_STAT(smb_fname_dst->st) is therefore true and, with replace_if_exists set, the rename is allowed to proceed. smb_fname_dst_rel is built from get_original_lcomp(), i.e. the literal name as sent by the client, and that is what reaches SMB_VFS_RENAMEAT() (source3/smbd/smb2_reply.c, line 1543 in master). Since that literal name does not exist on disk, the kernel creates a new entry and the original file is left untouched. The existing guard does not cover this case: SET_STAT_INVALID() is only reached when source and destination differ merely in case, i.e. a pure case change of one file. Here the source is an unrelated .tmp name, so that branch is never taken. --- Impact --- Observed in production across several unrelated customers after upgrading a fleet of Samba file servers from 4.18 to 4.24.3. Roaming profiles fail to load and users fall back to temporary profiles: duplicate .lnk entries differing only in case accumulate under AppData\Roaming\Microsoft\Windows\Recent, and the Windows User Profile Service cannot reconcile them when copying to NTFS at logon (events 1509 then 1511, access denied). No configuration workaround was found: "case sensitive", "smb3 unix extensions" and "smb3 directory leases" have no effect. The bug requires a client that writes via a temporary file and then renames over a target already present under a different case, which may explain why it has gone unreported.
Can you try the patches from https://gitlab.com/samba-team/samba/-/merge_requests/4730?
This bug was referenced in samba master: a798b16fc76b968d8af64409e6365791bbd62419 15f64c46c935b428d1827a170ee4082694464c19
Confirmed fixed. Tested on Ubuntu 22.04.5 / ext4, upstream build, --enable-debug --without-ad-dc --bundled-libraries=ALL, plain share (no VFS objects, no AD), Windows client calling MoveFileEx(). Baseline, master at 972663c5772 (4.26.0pre1), without the patch: case change only, MoveFileEx("dzdz.lnk", "DZDZ.lnk", REPLACE_EXISTING) -> OK: single file DZDZ.lnk, content "old" replace, MoveFileEx("{tmp}.tmp", "DZDZ.lnk", REPLACE_EXISTING) -> BUG: returned TRUE, two entries 917512 dzdz.lnk = "old" 917513 DZDZ.lnk = "new" no replace flag, MoveFileEx("{tmp}.tmp", "DZDZ.lnk", 0) -> OK: returned FALSE, error 183, both files untouched Same tree with 39d11db6b26 and 47e97bc713f applied: case change only -> unchanged, single file DZDZ.lnk, content "old" replace -> FIXED: returned TRUE, single entry 917513 dzdz.lnk = "new" The surviving entry keeps the on-disk capitalization, and the content is replaced. no replace flag -> unchanged, returned FALSE, error 183 No regression observed on the pure case-change path or on the name-collision path. Thanks for the very quick turnaround!
One question, if I may: would it be possible to consider a backport to v4-24-stable and v4-25-stable? We run 4.24.3 on production file servers, where this affects roaming profile loading for end users. I realise that is our problem to manage and not a reason to rush anything -- I am only mentioning it in case it helps judge how far the issue reaches beyond our setup. If it would be useful, I still have the test environment in place and I am happy to test the patches on top of v4-24-stable or v4-25-stable and report back, with the same three scenarios as above. Either way, thank you again for looking at this so quickly.
Created attachment 19270 [details] patch from master for v4-25-test
Created attachment 19271 [details] patch from master for v4-24-test
(In reply to Aurélien Merle from comment #4) Once approved backports should be available with the next release from v4.25 and v4.24.
This bug was referenced in samba v4-24-test: 132c18f8925bbe0efbae538c536436e098c30719 31e9fe26a360a41e28c7f448de84c5f81d6de919
Thanks for preparing the backports. I tested attachment 19271 [details] (v4-24-test). It applies cleanly with git am on top of tag samba-4.24.7, which is currently the head of v4-24-stable, so no adjustment was needed. Tested on Ubuntu 22.04.5 / ext4, built with our production configure options, on a domain-joined member server with real shares and ACLs. Windows client calling MoveFileEx(), three scenarios: 1. case change only, MoveFileEx("dzdz.lnk", "DZDZ.lnk", REPLACE_EXISTING) -> single file DZDZ.lnk, content "old" -- unchanged, no regression 2. replace, MoveFileEx("{tmp}.tmp", "DZDZ.lnk", REPLACE_EXISTING) -> FIXED: single entry dzdz.lnk, content "new" The surviving entry keeps the on-disk capitalization and the content is replaced. Before the patch this produced two entries. 3. no replace flag, MoveFileEx("{tmp}.tmp", "DZDZ.lnk", 0) -> returned FALSE, error 183, both files untouched -- unchanged So: fix confirmed, and no regression on the pure case-change path or on the name-collision path. I have not tested attachment 19270 [details] (v4-25-test), as we do not run 4.25 -- happy to do so if that would help.