Commit 1dfd062c authored by Namjae Jeon's avatar Namjae Jeon Committed by Steve French
Browse files

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: 18b4fac5 ("ksmbd: fix use-after-free in smb_break_all_levII_oplock()")
Cc: stable@vger.kernel.org
Signed-off-by: default avatarNamjae Jeon <linkinjeon@kernel.org>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 40955015
Loading
Loading
Loading
Loading
+21 −8
Original line number Diff line number Diff line
@@ -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 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
	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;
}

+3 −2
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ struct oplock_info {
	struct list_head        lease_entry;
	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 {