Skip to content

Commit 48087fe

Browse files
committed
smb: client: fix data loss due to broken rename(2)
JIRA: https://issues.redhat.com/browse/RHEL-114295 commit c5ea306 Author: Paulo Alcantara <pc@manguebit.org> Date: Sun Sep 7 21:24:06 2025 -0300 smb: client: fix data loss due to broken rename(2) Rename of open files in SMB2+ has been broken for a very long time, resulting in data loss as the CIFS client would fail the rename(2) call with -ENOENT and then removing the target file. Fix this by implementing ->rename_pending_delete() for SMB2+, which will rename busy files to random filenames (e.g. silly rename) during unlink(2) or rename(2), and then marking them to delete-on-close. Besides, introduce a FIND_WR_NO_PENDING_DELETE flag to prevent open(2) from reusing open handles that had been marked as delete pending. Handle it in cifs_get_readable_path() as well. Reported-by: Jean-Baptiste Denis <jbdenis@pasteur.fr> Closes: https://marc.info/?i=16aeb380-30d4-4551-9134-4e7d1dc833c0@pasteur.fr Reviewed-by: David Howells <dhowells@redhat.com> Cc: stable@vger.kernel.org Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> Cc: Frank Sorenson <sorenson@redhat.com> Cc: Olga Kornievskaia <okorniev@redhat.com> Cc: Benjamin Coddington <bcodding@redhat.com> Cc: Scott Mayhew <smayhew@redhat.com> Cc: linux-cifs@vger.kernel.org Signed-off-by: Steve French <stfrench@microsoft.com> Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
1 parent c89f97a commit 48087fe

File tree

8 files changed

+268
-72
lines changed

8 files changed

+268
-72
lines changed

fs/smb/client/cifsglob.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
#define SMB_INTERFACE_POLL_INTERVAL 600
8888

8989
/* maximum number of PDUs in one compound */
90-
#define MAX_COMPOUND 7
90+
#define MAX_COMPOUND 10
9191

9292
/*
9393
* Default number of credits to keep available for SMB3.
@@ -1938,9 +1938,12 @@ static inline bool is_replayable_error(int error)
19381938

19391939

19401940
/* cifs_get_writable_file() flags */
1941-
#define FIND_WR_ANY 0
1942-
#define FIND_WR_FSUID_ONLY 1
1943-
#define FIND_WR_WITH_DELETE 2
1941+
enum cifs_writable_file_flags {
1942+
FIND_WR_ANY = 0U,
1943+
FIND_WR_FSUID_ONLY = (1U << 0),
1944+
FIND_WR_WITH_DELETE = (1U << 1),
1945+
FIND_WR_NO_PENDING_DELETE = (1U << 2),
1946+
};
19441947

19451948
#define MID_FREE 0
19461949
#define MID_REQUEST_ALLOCATED 1
@@ -2374,6 +2377,8 @@ struct smb2_compound_vars {
23742377
struct kvec qi_iov;
23752378
struct kvec io_iov[SMB2_IOCTL_IOV_SIZE];
23762379
struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE];
2380+
struct kvec unlink_iov[SMB2_SET_INFO_IOV_SIZE];
2381+
struct kvec rename_iov[SMB2_SET_INFO_IOV_SIZE];
23772382
struct kvec close_iov;
23782383
struct smb2_file_rename_info_hdr rename_info;
23792384
struct smb2_file_link_info_hdr link_info;

fs/smb/client/file.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,10 @@ int cifs_open(struct inode *inode, struct file *file)
681681

682682
/* Get the cached handle as SMB2 close is deferred */
683683
if (OPEN_FMODE(file->f_flags) & FMODE_WRITE) {
684-
rc = cifs_get_writable_path(tcon, full_path, FIND_WR_FSUID_ONLY, &cfile);
684+
rc = cifs_get_writable_path(tcon, full_path,
685+
FIND_WR_FSUID_ONLY |
686+
FIND_WR_NO_PENDING_DELETE,
687+
&cfile);
685688
} else {
686689
rc = cifs_get_readable_path(tcon, full_path, &cfile);
687690
}
@@ -2286,6 +2289,9 @@ cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
22862289
continue;
22872290
if (with_delete && !(open_file->fid.access & DELETE))
22882291
continue;
2292+
if ((flags & FIND_WR_NO_PENDING_DELETE) &&
2293+
open_file->status_file_deleted)
2294+
continue;
22892295
if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
22902296
if (!open_file->invalidHandle) {
22912297
/* found a good writable file */
@@ -2403,6 +2409,16 @@ cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
24032409
spin_unlock(&tcon->open_file_lock);
24042410
free_dentry_path(page);
24052411
*ret_file = find_readable_file(cinode, 0);
2412+
if (*ret_file) {
2413+
spin_lock(&cinode->open_file_lock);
2414+
if ((*ret_file)->status_file_deleted) {
2415+
spin_unlock(&cinode->open_file_lock);
2416+
cifsFileInfo_put(*ret_file);
2417+
*ret_file = NULL;
2418+
} else {
2419+
spin_unlock(&cinode->open_file_lock);
2420+
}
2421+
}
24062422
return *ret_file ? 0 : -ENOENT;
24072423
}
24082424

fs/smb/client/inode.c

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,7 @@ cifs_drop_nlink(struct inode *inode)
19121912
* but will return the EACCES to the caller. Note that the VFS does not call
19131913
* unlink on negative dentries currently.
19141914
*/
1915-
int cifs_unlink(struct inode *dir, struct dentry *dentry)
1915+
static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyrename)
19161916
{
19171917
int rc = 0;
19181918
unsigned int xid;
@@ -1983,7 +1983,11 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
19831983
goto psx_del_no_retry;
19841984
}
19851985

1986-
rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry);
1986+
if (sillyrename || (server->vals->protocol_id > SMB10_PROT_ID &&
1987+
d_is_positive(dentry) && d_count(dentry) > 2))
1988+
rc = -EBUSY;
1989+
else
1990+
rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry);
19871991

19881992
psx_del_no_retry:
19891993
if (!rc) {
@@ -2051,6 +2055,11 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
20512055
return rc;
20522056
}
20532057

2058+
int cifs_unlink(struct inode *dir, struct dentry *dentry)
2059+
{
2060+
return __cifs_unlink(dir, dentry, false);
2061+
}
2062+
20542063
static int
20552064
cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode,
20562065
const char *full_path, struct cifs_sb_info *cifs_sb,
@@ -2338,14 +2347,16 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
23382347
rc = server->ops->rmdir(xid, tcon, full_path, cifs_sb);
23392348
cifs_put_tlink(tlink);
23402349

2350+
cifsInode = CIFS_I(d_inode(direntry));
2351+
23412352
if (!rc) {
2353+
set_bit(CIFS_INO_DELETE_PENDING, &cifsInode->flags);
23422354
spin_lock(&d_inode(direntry)->i_lock);
23432355
i_size_write(d_inode(direntry), 0);
23442356
clear_nlink(d_inode(direntry));
23452357
spin_unlock(&d_inode(direntry)->i_lock);
23462358
}
23472359

2348-
cifsInode = CIFS_I(d_inode(direntry));
23492360
/* force revalidate to go get info when needed */
23502361
cifsInode->time = 0;
23512362

@@ -2438,8 +2449,11 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry,
24382449
}
24392450
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
24402451
do_rename_exit:
2441-
if (rc == 0)
2452+
if (rc == 0) {
24422453
d_move(from_dentry, to_dentry);
2454+
/* Force a new lookup */
2455+
d_drop(from_dentry);
2456+
}
24432457
cifs_put_tlink(tlink);
24442458
return rc;
24452459
}
@@ -2450,6 +2464,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
24502464
struct dentry *target_dentry, unsigned int flags)
24512465
{
24522466
const char *from_name, *to_name;
2467+
struct TCP_Server_Info *server;
24532468
void *page1, *page2;
24542469
struct cifs_sb_info *cifs_sb;
24552470
struct tcon_link *tlink;
@@ -2485,6 +2500,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
24852500
if (IS_ERR(tlink))
24862501
return PTR_ERR(tlink);
24872502
tcon = tlink_tcon(tlink);
2503+
server = tcon->ses->server;
24882504

24892505
page1 = alloc_dentry_path();
24902506
page2 = alloc_dentry_path();
@@ -2569,19 +2585,53 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
25692585

25702586
unlink_target:
25712587
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
2572-
2573-
/* Try unlinking the target dentry if it's not negative */
2574-
if (d_really_is_positive(target_dentry) && (rc == -EACCES || rc == -EEXIST)) {
2575-
if (d_is_dir(target_dentry))
2576-
tmprc = cifs_rmdir(target_dir, target_dentry);
2577-
else
2578-
tmprc = cifs_unlink(target_dir, target_dentry);
2579-
if (tmprc)
2580-
goto cifs_rename_exit;
2581-
rc = cifs_do_rename(xid, source_dentry, from_name,
2582-
target_dentry, to_name);
2583-
if (!rc)
2584-
rehash = false;
2588+
if (d_really_is_positive(target_dentry)) {
2589+
if (!rc) {
2590+
struct inode *inode = d_inode(target_dentry);
2591+
/*
2592+
* Samba and ksmbd servers allow renaming a target
2593+
* directory that is open, so make sure to update
2594+
* ->i_nlink and then mark it as delete pending.
2595+
*/
2596+
if (S_ISDIR(inode->i_mode)) {
2597+
drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb);
2598+
spin_lock(&inode->i_lock);
2599+
i_size_write(inode, 0);
2600+
clear_nlink(inode);
2601+
spin_unlock(&inode->i_lock);
2602+
set_bit(CIFS_INO_DELETE_PENDING, &CIFS_I(inode)->flags);
2603+
CIFS_I(inode)->time = 0; /* force reval */
2604+
inode_set_ctime_current(inode);
2605+
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
2606+
}
2607+
} else if (rc == -EACCES || rc == -EEXIST) {
2608+
/*
2609+
* Rename failed, possibly due to a busy target.
2610+
* Retry it by unliking the target first.
2611+
*/
2612+
if (d_is_dir(target_dentry)) {
2613+
tmprc = cifs_rmdir(target_dir, target_dentry);
2614+
} else {
2615+
tmprc = __cifs_unlink(target_dir, target_dentry,
2616+
server->vals->protocol_id > SMB10_PROT_ID);
2617+
}
2618+
if (tmprc) {
2619+
/*
2620+
* Some servers will return STATUS_ACCESS_DENIED
2621+
* or STATUS_DIRECTORY_NOT_EMPTY when failing to
2622+
* rename a non-empty directory. Make sure to
2623+
* propagate the appropriate error back to
2624+
* userspace.
2625+
*/
2626+
if (tmprc == -EEXIST || tmprc == -ENOTEMPTY)
2627+
rc = tmprc;
2628+
goto cifs_rename_exit;
2629+
}
2630+
rc = cifs_do_rename(xid, source_dentry, from_name,
2631+
target_dentry, to_name);
2632+
if (!rc)
2633+
rehash = false;
2634+
}
25852635
}
25862636

25872637
/* force revalidate to go get info when needed */
@@ -2610,6 +2660,8 @@ cifs_dentry_needs_reval(struct dentry *dentry)
26102660
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
26112661
struct cached_fid *cfid = NULL;
26122662

2663+
if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags))
2664+
return false;
26132665
if (cifs_i->time == 0)
26142666
return true;
26152667

fs/smb/client/smb2glob.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ enum smb2_compound_ops {
3030
SMB2_OP_QUERY_DIR,
3131
SMB2_OP_MKDIR,
3232
SMB2_OP_RENAME,
33-
SMB2_OP_DELETE,
3433
SMB2_OP_HARDLINK,
3534
SMB2_OP_SET_EOF,
36-
SMB2_OP_RMDIR,
35+
SMB2_OP_UNLINK,
3736
SMB2_OP_POSIX_QUERY_INFO,
3837
SMB2_OP_SET_REPARSE,
3938
SMB2_OP_GET_REPARSE,

0 commit comments

Comments
 (0)