Skip to content

Commit

Permalink
ksmbd: fix use-after-free in smb_break_all_levII_oplock()
Browse files Browse the repository at this point in the history
commit 18b4fac upstream.

There is a room in smb_break_all_levII_oplock that can cause racy issues
when unlocking in the middle of the loop. This patch use read lock
to protect whole loop.

Cc: stable@vger.kernel.org
Reported-by: Norbert Szetei <norbert@doyensec.com>
Tested-by: Norbert Szetei <norbert@doyensec.com>
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>
  • Loading branch information
Namjae Jeon authored and Greg Kroah-Hartman committed Apr 25, 2025
1 parent 6e30c0e commit d54ab15
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 21 deletions.
29 changes: 9 additions & 20 deletions fs/smb/server/oplock.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,6 @@ static void free_opinfo(struct oplock_info *opinfo)
kfree(opinfo);
}

static inline void opinfo_free_rcu(struct rcu_head *rcu_head)
{
struct oplock_info *opinfo;

opinfo = container_of(rcu_head, struct oplock_info, rcu_head);
free_opinfo(opinfo);
}

struct oplock_info *opinfo_get(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
Expand All @@ -157,8 +149,8 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
if (list_empty(&ci->m_op_list))
return NULL;

rcu_read_lock();
opinfo = list_first_or_null_rcu(&ci->m_op_list, struct oplock_info,
down_read(&ci->m_lock);
opinfo = list_first_entry(&ci->m_op_list, struct oplock_info,
op_entry);
if (opinfo) {
if (opinfo->conn == NULL ||
Expand All @@ -171,8 +163,7 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
}
}
}

rcu_read_unlock();
up_read(&ci->m_lock);

return opinfo;
}
Expand All @@ -185,15 +176,15 @@ void opinfo_put(struct oplock_info *opinfo)
if (!atomic_dec_and_test(&opinfo->refcount))
return;

call_rcu(&opinfo->rcu_head, opinfo_free_rcu);
free_opinfo(opinfo);
}

static void opinfo_add(struct oplock_info *opinfo)
{
struct ksmbd_inode *ci = opinfo->o_fp->f_ci;

down_write(&ci->m_lock);
list_add_rcu(&opinfo->op_entry, &ci->m_op_list);
list_add(&opinfo->op_entry, &ci->m_op_list);
up_write(&ci->m_lock);
}

Expand All @@ -207,7 +198,7 @@ static void opinfo_del(struct oplock_info *opinfo)
write_unlock(&lease_list_lock);
}
down_write(&ci->m_lock);
list_del_rcu(&opinfo->op_entry);
list_del(&opinfo->op_entry);
up_write(&ci->m_lock);
}

Expand Down Expand Up @@ -1347,8 +1338,8 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
ci = fp->f_ci;
op = opinfo_get(fp);

rcu_read_lock();
list_for_each_entry_rcu(brk_op, &ci->m_op_list, op_entry) {
down_read(&ci->m_lock);
list_for_each_entry(brk_op, &ci->m_op_list, op_entry) {
if (brk_op->conn == NULL)
continue;

Expand All @@ -1358,7 +1349,6 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
if (ksmbd_conn_releasing(brk_op->conn))
continue;

rcu_read_unlock();
if (brk_op->is_lease && (brk_op->o_lease->state &
(~(SMB2_LEASE_READ_CACHING_LE |
SMB2_LEASE_HANDLE_CACHING_LE)))) {
Expand Down Expand Up @@ -1388,9 +1378,8 @@ void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
oplock_break(brk_op, SMB2_OPLOCK_LEVEL_NONE, NULL);
next:
opinfo_put(brk_op);
rcu_read_lock();
}
rcu_read_unlock();
up_read(&ci->m_lock);

if (op)
opinfo_put(op);
Expand Down
1 change: 0 additions & 1 deletion fs/smb/server/oplock.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ 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_head;
};

struct lease_break_info {
Expand Down

0 comments on commit d54ab15

Please sign in to comment.