]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
cifs: open files should not hold ref on superblock
authorShyam Prasad N <sprasad@microsoft.com>
Wed, 4 Mar 2026 12:45:53 +0000 (18:15 +0530)
committerSteve French <stfrench@microsoft.com>
Wed, 4 Mar 2026 16:11:39 +0000 (10:11 -0600)
Today whenever we deal with a file, in addition to holding
a reference on the dentry, we also get a reference on the
superblock. This happens in two cases:
1. when a new cinode is allocated
2. when an oplock break is being processed

The reasoning for holding the superblock ref was to make sure
that when umount happens, if there are users of inodes and
dentries, it does not try to clean them up and wait for the
last ref to superblock to be dropped by last of such users.

But the side effect of doing that is that umount silently drops
a ref on the superblock and we could have deferred closes and
lease breaks still holding these refs.

Ideally, we should ensure that all of these users of inodes and
dentries are cleaned up at the time of umount, which is what this
code is doing.

This code change allows these code paths to use a ref on the
dentry (and hence the inode). That way, umount is
ensured to clean up SMB client resources when it's the last
ref on the superblock (For ex: when same objects are shared).

The code change also moves the call to close all the files in
deferred close list to the umount code path. It also waits for
oplock_break workers to be flushed before calling
kill_anon_super (which eventually frees up those objects).

Fixes: 24261fc23db9 ("cifs: delay super block destruction until all cifsFileInfo objects are gone")
Fixes: 705c79101ccf ("smb: client: fix use-after-free in cifs_oplock_break")
Cc: <stable@vger.kernel.org>
Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsfs.c
fs/smb/client/cifsproto.h
fs/smb/client/file.c
fs/smb/client/misc.c
fs/smb/client/trace.h

index 427558404aa5d297b4d6753ae722b0eba62988ce..b6e3db993cc64ec63709a8ec1aff30f9e468dbcb 100644 (file)
@@ -332,10 +332,14 @@ static void cifs_kill_sb(struct super_block *sb)
 
        /*
         * We need to release all dentries for the cached directories
-        * before we kill the sb.
+        * and close all deferred file handles before we kill the sb.
         */
        if (cifs_sb->root) {
                close_all_cached_dirs(cifs_sb);
+               cifs_close_all_deferred_files_sb(cifs_sb);
+
+               /* Wait for all pending oplock breaks to complete */
+               flush_workqueue(cifsoplockd_wq);
 
                /* finally release root dentry */
                dput(cifs_sb->root);
@@ -868,7 +872,6 @@ static void cifs_umount_begin(struct super_block *sb)
        spin_unlock(&tcon->tc_lock);
        spin_unlock(&cifs_tcp_ses_lock);
 
-       cifs_close_all_deferred_files(tcon);
        /* cancel_brl_requests(tcon); */ /* BB mark all brl mids as exiting */
        /* cancel_notify_requests(tcon); */
        if (tcon->ses && tcon->ses->server) {
index 96d6b5325aa33c713459b16a72f79f5acafb05c7..800a7e418c32675eb6b6ab7c478501d55f4c97aa 100644 (file)
@@ -261,6 +261,7 @@ void cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode);
 
 void cifs_close_all_deferred_files(struct cifs_tcon *tcon);
 
+void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb);
 void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
                                           struct dentry *dentry);
 
index f3ddcdf406c8650782aca07143b181f6a9fa34ba..cffcf82c1b6902360f95017ba2bebb9bacc768bf 100644 (file)
@@ -711,8 +711,6 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
        mutex_init(&cfile->fh_mutex);
        spin_lock_init(&cfile->file_info_lock);
 
-       cifs_sb_active(inode->i_sb);
-
        /*
         * If the server returned a read oplock and we have mandatory brlocks,
         * set oplock level to None.
@@ -767,7 +765,6 @@ static void cifsFileInfo_put_final(struct cifsFileInfo *cifs_file)
        struct inode *inode = d_inode(cifs_file->dentry);
        struct cifsInodeInfo *cifsi = CIFS_I(inode);
        struct cifsLockInfo *li, *tmp;
-       struct super_block *sb = inode->i_sb;
 
        /*
         * Delete any outstanding lock records. We'll lose them when the file
@@ -785,7 +782,6 @@ static void cifsFileInfo_put_final(struct cifsFileInfo *cifs_file)
 
        cifs_put_tlink(cifs_file->tlink);
        dput(cifs_file->dentry);
-       cifs_sb_deactive(sb);
        kfree(cifs_file->symlink_target);
        kfree(cifs_file);
 }
@@ -3163,12 +3159,6 @@ void cifs_oplock_break(struct work_struct *work)
        __u64 persistent_fid, volatile_fid;
        __u16 net_fid;
 
-       /*
-        * Hold a reference to the superblock to prevent it and its inodes from
-        * being freed while we are accessing cinode. Otherwise, _cifsFileInfo_put()
-        * may release the last reference to the sb and trigger inode eviction.
-        */
-       cifs_sb_active(sb);
        wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
                        TASK_UNINTERRUPTIBLE);
 
@@ -3253,7 +3243,6 @@ oplock_break_ack:
        cifs_put_tlink(tlink);
 out:
        cifs_done_oplock_break(cinode);
-       cifs_sb_deactive(sb);
 }
 
 static int cifs_swap_activate(struct swap_info_struct *sis,
index bc24c92b8b95320863ff4530fa55430c1c795436..2aff1cab6c31e3f8deb5d4e50958532e14460eff 100644 (file)
 #include "fs_context.h"
 #include "cached_dir.h"
 
+struct tcon_list {
+       struct list_head entry;
+       struct cifs_tcon *tcon;
+};
+
 /* The xid serves as a useful identifier for each incoming vfs request,
    in a similar way to the mid which is useful to track each sent smb,
    and CurrentXid can also provide a running counter (although it
@@ -554,6 +559,43 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
        }
 }
 
+void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb)
+{
+       struct rb_root *root = &cifs_sb->tlink_tree;
+       struct rb_node *node;
+       struct cifs_tcon *tcon;
+       struct tcon_link *tlink;
+       struct tcon_list *tmp_list, *q;
+       LIST_HEAD(tcon_head);
+
+       spin_lock(&cifs_sb->tlink_tree_lock);
+       for (node = rb_first(root); node; node = rb_next(node)) {
+               tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+               tcon = tlink_tcon(tlink);
+               if (IS_ERR(tcon))
+                       continue;
+               tmp_list = kmalloc_obj(struct tcon_list, GFP_ATOMIC);
+               if (tmp_list == NULL)
+                       break;
+               tmp_list->tcon = tcon;
+               /* Take a reference on tcon to prevent it from being freed */
+               spin_lock(&tcon->tc_lock);
+               ++tcon->tc_count;
+               trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
+                                   netfs_trace_tcon_ref_get_close_defer_files);
+               spin_unlock(&tcon->tc_lock);
+               list_add_tail(&tmp_list->entry, &tcon_head);
+       }
+       spin_unlock(&cifs_sb->tlink_tree_lock);
+
+       list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) {
+               cifs_close_all_deferred_files(tmp_list->tcon);
+               list_del(&tmp_list->entry);
+               cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_close_defer_files);
+               kfree(tmp_list);
+       }
+}
+
 void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
                                           struct dentry *dentry)
 {
index 9228f95cae2bd590f9e09a675b8add64937b390e..acfbb63086ea23eecc2ef5f19751fffd0c414c20 100644 (file)
        EM(netfs_trace_tcon_ref_get_cached_laundromat,  "GET Ch-Lau") \
        EM(netfs_trace_tcon_ref_get_cached_lease_break, "GET Ch-Lea") \
        EM(netfs_trace_tcon_ref_get_cancelled_close,    "GET Cn-Cls") \
+       EM(netfs_trace_tcon_ref_get_close_defer_files,  "GET Cl-Def") \
        EM(netfs_trace_tcon_ref_get_dfs_refer,          "GET DfsRef") \
        EM(netfs_trace_tcon_ref_get_find,               "GET Find  ") \
        EM(netfs_trace_tcon_ref_get_find_sess_tcon,     "GET FndSes") \
        EM(netfs_trace_tcon_ref_put_cancelled_close,    "PUT Cn-Cls") \
        EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \
        EM(netfs_trace_tcon_ref_put_cancelled_mid,      "PUT Cn-Mid") \
+       EM(netfs_trace_tcon_ref_put_close_defer_files,  "PUT Cl-Def") \
        EM(netfs_trace_tcon_ref_put_mnt_ctx,            "PUT MntCtx") \
        EM(netfs_trace_tcon_ref_put_dfs_refer,          "PUT DfsRfr") \
        EM(netfs_trace_tcon_ref_put_reconnect_server,   "PUT Reconn") \