]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
smb: client: fix race with concurrent opens in unlink(2)
authorPaulo Alcantara <pc@manguebit.org>
Fri, 8 Aug 2025 15:20:17 +0000 (12:20 -0300)
committerSteve French <stfrench@microsoft.com>
Sun, 10 Aug 2025 17:39:51 +0000 (12:39 -0500)
According to some logs reported by customers, CIFS client might end up
reporting unlinked files as existing in stat(2) due to concurrent
opens racing with unlink(2).

Besides sending the removal request to the server, the unlink process
could involve closing any deferred close as well as marking all
existing open handles as deleted to prevent them from deferring
closes, which increases the race window for potential concurrent
opens.

Fix this by unhashing the dentry in cifs_unlink() to prevent any
subsequent opens.  Any open attempts, while we're still unlinking,
will block on parent's i_rwsem.

Reported-by: Jay Shin <jaeshin@redhat.com>
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Reviewed-by: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-cifs@vger.kernel.org
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/inode.c

index 75be4b46bc6f18e40b879cf798e0fa107f594f51..cf9060f0fc08e4cb1a6dcc99d2eb6e69a5159300 100644 (file)
@@ -1943,15 +1943,24 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
        struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
        struct tcon_link *tlink;
        struct cifs_tcon *tcon;
+       __u32 dosattr = 0, origattr = 0;
        struct TCP_Server_Info *server;
        struct iattr *attrs = NULL;
-       __u32 dosattr = 0, origattr = 0;
+       bool rehash = false;
 
        cifs_dbg(FYI, "cifs_unlink, dir=0x%p, dentry=0x%p\n", dir, dentry);
 
        if (unlikely(cifs_forced_shutdown(cifs_sb)))
                return -EIO;
 
+       /* Unhash dentry in advance to prevent any concurrent opens */
+       spin_lock(&dentry->d_lock);
+       if (!d_unhashed(dentry)) {
+               __d_drop(dentry);
+               rehash = true;
+       }
+       spin_unlock(&dentry->d_lock);
+
        tlink = cifs_sb_tlink(cifs_sb);
        if (IS_ERR(tlink))
                return PTR_ERR(tlink);
@@ -2003,7 +2012,8 @@ psx_del_no_retry:
                        cifs_drop_nlink(inode);
                }
        } else if (rc == -ENOENT) {
-               d_drop(dentry);
+               if (simple_positive(dentry))
+                       d_delete(dentry);
        } else if (rc == -EBUSY) {
                if (server->ops->rename_pending_delete) {
                        rc = server->ops->rename_pending_delete(full_path,
@@ -2056,6 +2066,8 @@ unlink_out:
        kfree(attrs);
        free_xid(xid);
        cifs_put_tlink(tlink);
+       if (rehash)
+               d_rehash(dentry);
        return rc;
 }