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: 18b4fac5ef ("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>
This commit is contained in:
Namjae Jeon
2026-03-07 11:32:31 +09:00
committed by Steve French
parent 40955015fa
commit 1dfd062caa
2 changed files with 24 additions and 10 deletions

View File

@@ -120,7 +120,7 @@ static void free_lease(struct oplock_info *opinfo)
kfree(lease); kfree(lease);
} }
static void free_opinfo(struct oplock_info *opinfo) static void __free_opinfo(struct oplock_info *opinfo)
{ {
if (opinfo->is_lease) if (opinfo->is_lease)
free_lease(opinfo); free_lease(opinfo);
@@ -129,6 +129,18 @@ static void free_opinfo(struct oplock_info *opinfo)
kfree(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_get(struct ksmbd_file *fp)
{ {
struct oplock_info *opinfo; struct oplock_info *opinfo;
@@ -176,9 +188,9 @@ void opinfo_put(struct oplock_info *opinfo)
free_opinfo(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); down_write(&ci->m_lock);
list_add(&opinfo->op_entry, &ci->m_op_list); list_add(&opinfo->op_entry, &ci->m_op_list);
@@ -1277,20 +1289,21 @@ set_lev:
set_oplock_level(opinfo, req_op_level, lctx); set_oplock_level(opinfo, req_op_level, lctx);
out: out:
rcu_assign_pointer(fp->f_opinfo, opinfo);
opinfo->o_fp = fp;
opinfo_count_inc(fp); opinfo_count_inc(fp);
opinfo_add(opinfo); opinfo_add(opinfo, fp);
if (opinfo->is_lease) { if (opinfo->is_lease) {
err = add_lease_global_list(opinfo); err = add_lease_global_list(opinfo);
if (err) if (err)
goto err_out; goto err_out;
} }
rcu_assign_pointer(fp->f_opinfo, opinfo);
opinfo->o_fp = fp;
return 0; return 0;
err_out: err_out:
free_opinfo(opinfo); __free_opinfo(opinfo);
return err; return err;
} }

View File

@@ -69,8 +69,9 @@ struct oplock_info {
struct lease *o_lease; struct lease *o_lease;
struct list_head op_entry; struct list_head op_entry;
struct list_head lease_entry; struct list_head lease_entry;
wait_queue_head_t oplock_q; /* Other server threads */ wait_queue_head_t oplock_q; /* Other server threads */
wait_queue_head_t oplock_brk; /* oplock breaking wait */ wait_queue_head_t oplock_brk; /* oplock breaking wait */
struct rcu_head rcu;
}; };
struct lease_break_info { struct lease_break_info {