]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ksmbd: fix use-after-free by using call_rcu() for oplock_info
authorNamjae Jeon <linkinjeon@kernel.org>
Sat, 7 Mar 2026 02:32:31 +0000 (11:32 +0900)
committerSteve French <stfrench@microsoft.com>
Mon, 9 Mar 2026 02:28:39 +0000 (21:28 -0500)
ksmbd currently frees oplock_info immediately using kfree(), even
though it is accessed under RCU read-side critical sections in places
like opinfo_get() and proc_show_files().

Since there is no RCU grace period delay between nullifying the pointer
and freeing the memory, a reader can still access oplock_info
structure after it has been freed. This can leads to a use-after-free
especially in opinfo_get() where atomic_inc_not_zero() is called on
already freed memory.

Fix this by switching to deferred freeing using call_rcu().

Fixes: 18b4fac5ef17 ("ksmbd: fix use-after-free in smb_break_all_levII_oplock()")
Cc: stable@vger.kernel.org
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/oplock.c
fs/smb/server/oplock.h

index 09d9878db9cbfbbe36230ef49ec9bc806695dc6c..8c9aa17384f3ec7686b1feefa1cf68a5d1341567 100644 (file)
@@ -120,7 +120,7 @@ static void free_lease(struct oplock_info *opinfo)
        kfree(lease);
 }
 
-static void free_opinfo(struct oplock_info *opinfo)
+static void __free_opinfo(struct oplock_info *opinfo)
 {
        if (opinfo->is_lease)
                free_lease(opinfo);
@@ -129,6 +129,18 @@ static void free_opinfo(struct oplock_info *opinfo)
        kfree(opinfo);
 }
 
+static void free_opinfo_rcu(struct rcu_head *rcu)
+{
+       struct oplock_info *opinfo = container_of(rcu, struct oplock_info, rcu);
+
+       __free_opinfo(opinfo);
+}
+
+static void free_opinfo(struct oplock_info *opinfo)
+{
+       call_rcu(&opinfo->rcu, free_opinfo_rcu);
+}
+
 struct oplock_info *opinfo_get(struct ksmbd_file *fp)
 {
        struct oplock_info *opinfo;
@@ -176,9 +188,9 @@ void opinfo_put(struct oplock_info *opinfo)
        free_opinfo(opinfo);
 }
 
-static void opinfo_add(struct oplock_info *opinfo)
+static void opinfo_add(struct oplock_info *opinfo, struct ksmbd_file *fp)
 {
-       struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
+       struct ksmbd_inode *ci = fp->f_ci;
 
        down_write(&ci->m_lock);
        list_add(&opinfo->op_entry, &ci->m_op_list);
@@ -1277,20 +1289,21 @@ set_lev:
        set_oplock_level(opinfo, req_op_level, lctx);
 
 out:
-       rcu_assign_pointer(fp->f_opinfo, opinfo);
-       opinfo->o_fp = fp;
-
        opinfo_count_inc(fp);
-       opinfo_add(opinfo);
+       opinfo_add(opinfo, fp);
+
        if (opinfo->is_lease) {
                err = add_lease_global_list(opinfo);
                if (err)
                        goto err_out;
        }
 
+       rcu_assign_pointer(fp->f_opinfo, opinfo);
+       opinfo->o_fp = fp;
+
        return 0;
 err_out:
-       free_opinfo(opinfo);
+       __free_opinfo(opinfo);
        return err;
 }
 
index 9a56eaadd0dd8f063afde5a0f4abc2ce83792aa6..921e3199e4df4355d9e6154ca96181dd899b070a 100644 (file)
@@ -69,8 +69,9 @@ struct oplock_info {
        struct lease            *o_lease;
        struct list_head        op_entry;
        struct list_head        lease_entry;
-       wait_queue_head_t oplock_q; /* Other server threads */
-       wait_queue_head_t oplock_brk; /* oplock breaking wait */
+       wait_queue_head_t       oplock_q; /* Other server threads */
+       wait_queue_head_t       oplock_brk; /* oplock breaking wait */
+       struct rcu_head         rcu;
 };
 
 struct lease_break_info {