]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
smb: client: Avoid race in open_cached_dir with lease breaks
authorPaul Aurich <paul@darkrain42.org>
Wed, 7 May 2025 05:28:09 +0000 (22:28 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 18 May 2025 06:24:08 +0000 (08:24 +0200)
commit 3ca02e63edccb78ef3659bebc68579c7224a6ca2 upstream.

A pre-existing valid cfid returned from find_or_create_cached_dir might
race with a lease break, meaning open_cached_dir doesn't consider it
valid, and thinks it's newly-constructed. This leaks a dentry reference
if the allocation occurs before the queued lease break work runs.

Avoid the race by extending holding the cfid_list_lock across
find_or_create_cached_dir and when the result is checked.

Cc: stable@vger.kernel.org
Reviewed-by: Henrique Carvalho <henrique.carvalho@suse.com>
Signed-off-by: Paul Aurich <paul@darkrain42.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/smb/client/cached_dir.c

index 9c0ef4195b5829dad2e8e4d72eed28fe9e458dd1..74979466729535b3930f519a71420868b9ea1741 100644 (file)
@@ -29,7 +29,6 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
 {
        struct cached_fid *cfid;
 
-       spin_lock(&cfids->cfid_list_lock);
        list_for_each_entry(cfid, &cfids->entries, entry) {
                if (!strcmp(cfid->path, path)) {
                        /*
@@ -38,25 +37,20 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
                         * being deleted due to a lease break.
                         */
                        if (!cfid->time || !cfid->has_lease) {
-                               spin_unlock(&cfids->cfid_list_lock);
                                return NULL;
                        }
                        kref_get(&cfid->refcount);
-                       spin_unlock(&cfids->cfid_list_lock);
                        return cfid;
                }
        }
        if (lookup_only) {
-               spin_unlock(&cfids->cfid_list_lock);
                return NULL;
        }
        if (cfids->num_entries >= max_cached_dirs) {
-               spin_unlock(&cfids->cfid_list_lock);
                return NULL;
        }
        cfid = init_cached_dir(path);
        if (cfid == NULL) {
-               spin_unlock(&cfids->cfid_list_lock);
                return NULL;
        }
        cfid->cfids = cfids;
@@ -74,7 +68,6 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
         */
        cfid->has_lease = true;
 
-       spin_unlock(&cfids->cfid_list_lock);
        return cfid;
 }
 
@@ -185,8 +178,10 @@ replay_again:
        if (!utf16_path)
                return -ENOMEM;
 
+       spin_lock(&cfids->cfid_list_lock);
        cfid = find_or_create_cached_dir(cfids, path, lookup_only, tcon->max_cached_dirs);
        if (cfid == NULL) {
+               spin_unlock(&cfids->cfid_list_lock);
                kfree(utf16_path);
                return -ENOENT;
        }
@@ -195,7 +190,6 @@ replay_again:
         * Otherwise, it is either a new entry or laundromat worker removed it
         * from @cfids->entries.  Caller will put last reference if the latter.
         */
-       spin_lock(&cfids->cfid_list_lock);
        if (cfid->has_lease && cfid->time) {
                spin_unlock(&cfids->cfid_list_lock);
                *ret_cfid = cfid;