]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
cifs: fix cifsFileInfo leak on kmalloc failure in deferred close drain paths
authorFrank Sorenson <sorenson@redhat.com>
Tue, 21 Jul 2026 23:55:52 +0000 (18:55 -0500)
committerSteve French <stfrench@microsoft.com>
Wed, 22 Jul 2026 01:19:11 +0000 (20:19 -0500)
In cifs_close_deferred_file(), cifs_close_all_deferred_files(), and
cifs_close_deferred_file_under_dentry(), when a pending deferred close
is cancelled via cancel_delayed_work(), the subsequent kmalloc_obj() to
add the file to the local processing list may fail under memory pressure.
The loop breaks immediately, but the cancelled work is no longer pending
(it would have called _cifsFileInfo_put()), and the cfile is never added
to file_head for processing.  The cifsFileInfo reference and the open
server handle both leak.

Fix by saving the cfile that failed allocation in a local variable,
breaking as before, and calling _cifsFileInfo_put() on it after
releasing the lock.  Any files later in the iteration are unaffected
since their deferred work is still pending and will fire normally.

Fixes: e3fc065682eb ("cifs: Deferred close performance improvements")
Signed-off-by: Frank Sorenson <sorenson@redhat.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/misc.c

index fa8cfc4d97f32069368fb3c4726379db27f77e5b..6edebc0807eaa594073803dedb1df84f58080437 100644 (file)
@@ -497,7 +497,7 @@ cifs_del_deferred_close(struct cifsFileInfo *cfile)
 void
 cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
 {
-       struct cifsFileInfo *cfile = NULL;
+       struct cifsFileInfo *cfile = NULL, *failed_cfile = NULL;
        struct file_list *tmp_list, *tmp_next_list;
        LIST_HEAD(file_head);
 
@@ -514,8 +514,10 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
 
                                tmp_list = kmalloc_obj(struct file_list,
                                                       GFP_ATOMIC);
-                               if (tmp_list == NULL)
+                               if (tmp_list == NULL) {
+                                       failed_cfile = cfile;
                                        break;
+                               }
                                tmp_list->cfile = cfile;
                                list_add_tail(&tmp_list->list, &file_head);
                        }
@@ -523,6 +525,15 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
        }
        spin_unlock(&cifs_inode->open_file_lock);
 
+       if (failed_cfile) {
+               if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
+                       /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
+                       smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
+                                         jiffies);
+               }
+               _cifsFileInfo_put(failed_cfile, false, false);
+       }
+
        list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
                struct cifsFileInfo *cfile = tmp_list->cfile;
 
@@ -540,7 +551,7 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
 void
 cifs_close_all_deferred_files(struct cifs_tcon *tcon)
 {
-       struct cifsFileInfo *cfile;
+       struct cifsFileInfo *cfile, *failed_cfile = NULL;
        struct file_list *tmp_list, *tmp_next_list;
        LIST_HEAD(file_head);
 
@@ -554,8 +565,10 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
 
                                tmp_list = kmalloc_obj(struct file_list,
                                                       GFP_ATOMIC);
-                               if (tmp_list == NULL)
+                               if (tmp_list == NULL) {
+                                       failed_cfile = cfile;
                                        break;
+                               }
                                tmp_list->cfile = cfile;
                                list_add_tail(&tmp_list->list, &file_head);
                        }
@@ -563,6 +576,15 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
        }
        spin_unlock(&tcon->open_file_lock);
 
+       if (failed_cfile) {
+               if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
+                       /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
+                       smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
+                                         jiffies);
+               }
+               _cifsFileInfo_put(failed_cfile, true, false);
+       }
+
        list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
                struct cifsFileInfo *cfile = tmp_list->cfile;
 
@@ -618,7 +640,7 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
                                           struct dentry *dentry)
 {
        struct file_list *tmp_list, *tmp_next_list;
-       struct cifsFileInfo *cfile;
+       struct cifsFileInfo *cfile, *failed_cfile = NULL;
        LIST_HEAD(file_head);
 
        spin_lock(&tcon->open_file_lock);
@@ -631,14 +653,25 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
                        spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
 
                        tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC);
-                       if (tmp_list == NULL)
+                       if (tmp_list == NULL) {
+                               failed_cfile = cfile;
                                break;
+                       }
                        tmp_list->cfile = cfile;
                        list_add_tail(&tmp_list->list, &file_head);
                }
        }
        spin_unlock(&tcon->open_file_lock);
 
+       if (failed_cfile) {
+               if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
+                       /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
+                       smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
+                                         jiffies);
+               }
+               _cifsFileInfo_put(failed_cfile, true, false);
+       }
+
        list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
                struct cifsFileInfo *cfile = tmp_list->cfile;