summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNamjae Jeon <linkinjeon@kernel.org>2026-03-07 11:32:31 +0900
committerSteve French <stfrench@microsoft.com>2026-03-08 21:28:39 -0500
commit1dfd062caa165ec9d7ee0823087930f3ab8a6294 (patch)
treef2aede1eaf756939273d2245c7397b2df476bc7a
parent40955015fae4908157ac6c959ea696d05e6e9b31 (diff)
downloadlinux-1dfd062caa165ec9d7ee0823087930f3ab8a6294.tar.gz
linux-1dfd062caa165ec9d7ee0823087930f3ab8a6294.zip
ksmbd: fix use-after-free by using call_rcu() for oplock_info
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>
-rw-r--r--fs/smb/server/oplock.c29
-rw-r--r--fs/smb/server/oplock.h5
2 files changed, 24 insertions, 10 deletions
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 09d9878db9cb..8c9aa17384f3 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -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;
}
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index 9a56eaadd0dd..921e3199e4df 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -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 {