Skip to content

Commit d13eb2f

Browse files
committed
smb: client: fix data loss due to broken rename(2)
JIRA: https://issues.redhat.com/browse/RHEL-108683 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 a5e74b4 commit d13eb2f

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.
@@ -1881,9 +1881,12 @@ static inline bool is_replayable_error(int error)
18811881

18821882

18831883
/* cifs_get_writable_file() flags */
1884-
#define FIND_WR_ANY 0
1885-
#define FIND_WR_FSUID_ONLY 1
1886-
#define FIND_WR_WITH_DELETE 2
1884+
enum cifs_writable_file_flags {
1885+
FIND_WR_ANY = 0U,
1886+
FIND_WR_FSUID_ONLY = (1U << 0),
1887+
FIND_WR_WITH_DELETE = (1U << 1),
1888+
FIND_WR_NO_PENDING_DELETE = (1U << 2),
1889+
};
18871890

18881891
#define MID_FREE 0
18891892
#define MID_REQUEST_ALLOCATED 1
@@ -2339,6 +2342,8 @@ struct smb2_compound_vars {
23392342
struct kvec qi_iov;
23402343
struct kvec io_iov[SMB2_IOCTL_IOV_SIZE];
23412344
struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE];
2345+
struct kvec unlink_iov[SMB2_SET_INFO_IOV_SIZE];
2346+
struct kvec rename_iov[SMB2_SET_INFO_IOV_SIZE];
23422347
struct kvec close_iov;
23432348
struct smb2_file_rename_info_hdr rename_info;
23442349
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
@@ -998,7 +998,10 @@ int cifs_open(struct inode *inode, struct file *file)
998998

999999
/* Get the cached handle as SMB2 close is deferred */
10001000
if (OPEN_FMODE(file->f_flags) & FMODE_WRITE) {
1001-
rc = cifs_get_writable_path(tcon, full_path, FIND_WR_FSUID_ONLY, &cfile);
1001+
rc = cifs_get_writable_path(tcon, full_path,
1002+
FIND_WR_FSUID_ONLY |
1003+
FIND_WR_NO_PENDING_DELETE,
1004+
&cfile);
10021005
} else {
10031006
rc = cifs_get_readable_path(tcon, full_path, &cfile);
10041007
}
@@ -2530,6 +2533,9 @@ cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
25302533
continue;
25312534
if (with_delete && !(open_file->fid.access & DELETE))
25322535
continue;
2536+
if ((flags & FIND_WR_NO_PENDING_DELETE) &&
2537+
open_file->status_file_deleted)
2538+
continue;
25332539
if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
25342540
if (!open_file->invalidHandle) {
25352541
/* found a good writable file */
@@ -2647,6 +2653,16 @@ cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
26472653
spin_unlock(&tcon->open_file_lock);
26482654
free_dentry_path(page);
26492655
*ret_file = find_readable_file(cinode, 0);
2656+
if (*ret_file) {
2657+
spin_lock(&cinode->open_file_lock);
2658+
if ((*ret_file)->status_file_deleted) {
2659+
spin_unlock(&cinode->open_file_lock);
2660+
cifsFileInfo_put(*ret_file);
2661+
*ret_file = NULL;
2662+
} else {
2663+
spin_unlock(&cinode->open_file_lock);
2664+
}
2665+
}
26502666
return *ret_file ? 0 : -ENOENT;
26512667
}
26522668

fs/smb/client/inode.c

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,7 +1931,7 @@ cifs_drop_nlink(struct inode *inode)
19311931
* but will return the EACCES to the caller. Note that the VFS does not call
19321932
* unlink on negative dentries currently.
19331933
*/
1934-
int cifs_unlink(struct inode *dir, struct dentry *dentry)
1934+
static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyrename)
19351935
{
19361936
int rc = 0;
19371937
unsigned int xid;
@@ -2003,7 +2003,11 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
20032003
goto psx_del_no_retry;
20042004
}
20052005

2006-
rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry);
2006+
if (sillyrename || (server->vals->protocol_id > SMB10_PROT_ID &&
2007+
d_is_positive(dentry) && d_count(dentry) > 2))
2008+
rc = -EBUSY;
2009+
else
2010+
rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry);
20072011

20082012
psx_del_no_retry:
20092013
if (!rc) {
@@ -2071,6 +2075,11 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
20712075
return rc;
20722076
}
20732077

2078+
int cifs_unlink(struct inode *dir, struct dentry *dentry)
2079+
{
2080+
return __cifs_unlink(dir, dentry, false);
2081+
}
2082+
20742083
static int
20752084
cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode,
20762085
const char *full_path, struct cifs_sb_info *cifs_sb,
@@ -2358,14 +2367,16 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
23582367
rc = server->ops->rmdir(xid, tcon, full_path, cifs_sb);
23592368
cifs_put_tlink(tlink);
23602369

2370+
cifsInode = CIFS_I(d_inode(direntry));
2371+
23612372
if (!rc) {
2373+
set_bit(CIFS_INO_DELETE_PENDING, &cifsInode->flags);
23622374
spin_lock(&d_inode(direntry)->i_lock);
23632375
i_size_write(d_inode(direntry), 0);
23642376
clear_nlink(d_inode(direntry));
23652377
spin_unlock(&d_inode(direntry)->i_lock);
23662378
}
23672379

2368-
cifsInode = CIFS_I(d_inode(direntry));
23692380
/* force revalidate to go get info when needed */
23702381
cifsInode->time = 0;
23712382

@@ -2458,8 +2469,11 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry,
24582469
}
24592470
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
24602471
do_rename_exit:
2461-
if (rc == 0)
2472+
if (rc == 0) {
24622473
d_move(from_dentry, to_dentry);
2474+
/* Force a new lookup */
2475+
d_drop(from_dentry);
2476+
}
24632477
cifs_put_tlink(tlink);
24642478
return rc;
24652479
}
@@ -2470,6 +2484,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
24702484
struct dentry *target_dentry, unsigned int flags)
24712485
{
24722486
const char *from_name, *to_name;
2487+
struct TCP_Server_Info *server;
24732488
void *page1, *page2;
24742489
struct cifs_sb_info *cifs_sb;
24752490
struct tcon_link *tlink;
@@ -2505,6 +2520,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
25052520
if (IS_ERR(tlink))
25062521
return PTR_ERR(tlink);
25072522
tcon = tlink_tcon(tlink);
2523+
server = tcon->ses->server;
25082524

25092525
page1 = alloc_dentry_path();
25102526
page2 = alloc_dentry_path();
@@ -2591,19 +2607,53 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
25912607

25922608
unlink_target:
25932609
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
2594-
2595-
/* Try unlinking the target dentry if it's not negative */
2596-
if (d_really_is_positive(target_dentry) && (rc == -EACCES || rc == -EEXIST)) {
2597-
if (d_is_dir(target_dentry))
2598-
tmprc = cifs_rmdir(target_dir, target_dentry);
2599-
else
2600-
tmprc = cifs_unlink(target_dir, target_dentry);
2601-
if (tmprc)
2602-
goto cifs_rename_exit;
2603-
rc = cifs_do_rename(xid, source_dentry, from_name,
2604-
target_dentry, to_name);
2605-
if (!rc)
2606-
rehash = false;
2610+
if (d_really_is_positive(target_dentry)) {
2611+
if (!rc) {
2612+
struct inode *inode = d_inode(target_dentry);
2613+
/*
2614+
* Samba and ksmbd servers allow renaming a target
2615+
* directory that is open, so make sure to update
2616+
* ->i_nlink and then mark it as delete pending.
2617+
*/
2618+
if (S_ISDIR(inode->i_mode)) {
2619+
drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb);
2620+
spin_lock(&inode->i_lock);
2621+
i_size_write(inode, 0);
2622+
clear_nlink(inode);
2623+
spin_unlock(&inode->i_lock);
2624+
set_bit(CIFS_INO_DELETE_PENDING, &CIFS_I(inode)->flags);
2625+
CIFS_I(inode)->time = 0; /* force reval */
2626+
inode_set_ctime_current(inode);
2627+
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
2628+
}
2629+
} else if (rc == -EACCES || rc == -EEXIST) {
2630+
/*
2631+
* Rename failed, possibly due to a busy target.
2632+
* Retry it by unliking the target first.
2633+
*/
2634+
if (d_is_dir(target_dentry)) {
2635+
tmprc = cifs_rmdir(target_dir, target_dentry);
2636+
} else {
2637+
tmprc = __cifs_unlink(target_dir, target_dentry,
2638+
server->vals->protocol_id > SMB10_PROT_ID);
2639+
}
2640+
if (tmprc) {
2641+
/*
2642+
* Some servers will return STATUS_ACCESS_DENIED
2643+
* or STATUS_DIRECTORY_NOT_EMPTY when failing to
2644+
* rename a non-empty directory. Make sure to
2645+
* propagate the appropriate error back to
2646+
* userspace.
2647+
*/
2648+
if (tmprc == -EEXIST || tmprc == -ENOTEMPTY)
2649+
rc = tmprc;
2650+
goto cifs_rename_exit;
2651+
}
2652+
rc = cifs_do_rename(xid, source_dentry, from_name,
2653+
target_dentry, to_name);
2654+
if (!rc)
2655+
rehash = false;
2656+
}
26072657
}
26082658

26092659
/* force revalidate to go get info when needed */
@@ -2629,6 +2679,8 @@ cifs_dentry_needs_reval(struct dentry *dentry)
26292679
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
26302680
struct cached_fid *cfid = NULL;
26312681

2682+
if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags))
2683+
return false;
26322684
if (cifs_i->time == 0)
26332685
return true;
26342686

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)