mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-04-03 12:05:13 +02:00
ksmbd: fix use-after-free by using call_rcu() for oplock_info
commit1dfd062caaupstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
54b48ae83d
commit
08aa9f3c8c
@@ -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);
|
||||
@@ -1279,20 +1291,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user