mirror of
https://github.com/torvalds/linux.git
synced 2026-09-11 20:13:02 +02:00
- Fix a tree connection use-after-free in smb2_tree_connect() by
balancing references across concurrent connect, disconnect, and
session logoff paths.
- Validate source and target ranges in COPYCHUNK requests before range
locking and copy operations.
- Fix an oplock break notification UAF by acquiring a connection
reference under ksmbd_inode lock and releasing it after the
notification work completes.
- Fix the sparc build by using an unsigned int for the atomic work
state, ensuring xchg() uses a supported four-byte operation.
-----BEGIN PGP SIGNATURE-----
iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmqan48WHGxpbmtpbmpl
b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCP41D/4gNSDDjbGg/Du5nNlNfd7x0/Ql
ARgdajcnTT/2rUrBkXb3hfNi7BvSHz8jShb7acnwcs9VbxF7cWMk0r+tkjpsONI9
hwUAXiqOQNkiUJZez+29WgiVuIqNjWSB9WKDGcA7J364Vnwm4M5a8y9wZHfV9ReQ
aXmVlNlWP6zosrXv4Ex2Eb1bUaYnB822ZrsMQBdiZireUlVUyi/MeWOIrjxt7xPJ
/nMTJcyDNIFjJJIQMZ/LjzIzvD82QO4LP3F8rlvHD2UIMQik6m0UXF3wPUZGPMbp
X3o/sPoZHF1KrVpOG4SR5Lvy6KHLtGDSP7bVVhw4ahdtUnyeAeHx/xo9+OKbsiBO
E4Sji35E8ZyIZ/xHMtOfSfA74W9ia0A0olWyG/mviptJ0RD8unddJP+D/L+EAD0I
2pQyt9YfwsXm/7FoQXzfbtyi8Z2gl5Jp+xNr6DOyzkIulsBxFOBjgqrUEaTV2/Dc
abqnVPLo4X9zqiE0HdSNS/go2STx9iox02blZ2wBmYNxEy4X22IheXbyN+wdtn8Q
Ts8PrE1W0DO8PjWL9TG8okRYY3tRd0AcAVffi6P+QqkRsQNRCduv0mnAsSY1EuU0
r/M2i5RpQ+JIBLQc4C53zOaAbBCyY1MSXUCgAs1BVyyUdqtjdPJbzyzNn6FDYVPA
HrPxSl+fmtBsH0C25A==
=2ZL4
-----END PGP SIGNATURE-----
Merge tag 'ksmbd-for-7.3-rc2-part2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb
Pull smb server fixes from Namjae Jeon:
- Fix a tree connection use-after-free in smb2_tree_connect() by
balancing references across concurrent connect, disconnect, and
session logoff paths.
- Validate source and target ranges in COPYCHUNK requests before range
locking and copy operations.
- Fix an oplock break notification UAF by acquiring a connection
reference under ksmbd_inode lock and releasing it after the
notification work completes.
- Fix the sparc build by using an unsigned int for the atomic work
state, ensuring xchg() uses a supported four-byte operation.
* tag 'ksmbd-for-7.3-rc2-part2' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/smb:
ksmbd: fix tree connection use-after-free in smb2_tree_connect()
ksmbd: validate COPYCHUNK source and target ranges
ksmbd: fix use-after-free in oplock break notification
ksmbd: fix sparc build with atomic work state
This commit is contained in:
commit
58f93a4b73
|
|
@ -82,7 +82,7 @@ struct ksmbd_work {
|
|||
/* Contiguous SMB2 compression transform owned by this work item. */
|
||||
void *compress_buf;
|
||||
|
||||
unsigned char state;
|
||||
unsigned int state;
|
||||
/* No response for cancelled request */
|
||||
bool send_no_response:1;
|
||||
/* Request is encrypted */
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
|
|||
down_write(&sess->tree_conns_lock);
|
||||
ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn,
|
||||
KSMBD_DEFAULT_GFP));
|
||||
if (!ret)
|
||||
atomic_inc(&tree_conn->refcount);
|
||||
up_write(&sess->tree_conns_lock);
|
||||
if (ret) {
|
||||
status.ret = -ENOMEM;
|
||||
|
|
@ -129,6 +131,12 @@ int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
|
|||
struct ksmbd_tree_connect *tree_conn)
|
||||
{
|
||||
down_write(&sess->tree_conns_lock);
|
||||
if (tree_conn->t_state == TREE_DISCONNECTED ||
|
||||
xa_load(&sess->tree_conns, tree_conn->id) != tree_conn) {
|
||||
up_write(&sess->tree_conns_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
tree_conn->t_state = TREE_DISCONNECTED;
|
||||
xa_erase(&sess->tree_conns, tree_conn->id);
|
||||
up_write(&sess->tree_conns_lock);
|
||||
|
||||
|
|
|
|||
|
|
@ -924,31 +924,69 @@ static void __smb2_oplock_break_noti(struct work_struct *wk)
|
|||
ksmbd_conn_put(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Select and pin the connection used for an oplock break before doing any
|
||||
* allocations which may sleep. The caller of oplock_break() holds a live
|
||||
* reference on ci (a file being opened, a file being operated on, or an
|
||||
* explicit ksmbd_inode_lookup_lock() reference in the parent lease break
|
||||
* paths), so the inode cannot be freed during the call and its lock is
|
||||
* reachable without dereferencing opinfo->o_fp, which is not pinned by
|
||||
* the oplock reference and may be freed by a concurrent close.
|
||||
*
|
||||
* opinfo->conn is cleared under ci->m_lock by session_fd_check() when the
|
||||
* durable handle owning the oplock is disconnected, reassigned by
|
||||
* ksmbd_reopen_durable_fd() under the same lock, and the last
|
||||
* ksmbd_conn_put() of the old connection frees it. Holding the read lock
|
||||
* excludes both writers, so the connection cannot be freed while it is
|
||||
* selected.
|
||||
*/
|
||||
static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo,
|
||||
struct ksmbd_inode *ci)
|
||||
{
|
||||
struct ksmbd_conn *conn;
|
||||
|
||||
down_read(&ci->m_lock);
|
||||
conn = READ_ONCE(opinfo->conn);
|
||||
if (conn && !ksmbd_conn_releasing(conn))
|
||||
conn = ksmbd_conn_get(conn);
|
||||
else
|
||||
conn = NULL;
|
||||
up_read(&ci->m_lock);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock
|
||||
* break command from server to client
|
||||
* @opinfo: oplock info object
|
||||
* @ci: inode owning the break target's oplock list, pinned by
|
||||
* the caller
|
||||
*
|
||||
* Return: 0 on success, otherwise error
|
||||
*/
|
||||
static int smb2_oplock_break_noti(struct oplock_info *opinfo)
|
||||
static int smb2_oplock_break_noti(struct oplock_info *opinfo,
|
||||
struct ksmbd_inode *ci)
|
||||
{
|
||||
struct ksmbd_conn *conn;
|
||||
struct oplock_break_info *br_info;
|
||||
int ret = 0;
|
||||
struct ksmbd_work *work;
|
||||
|
||||
conn = READ_ONCE(opinfo->conn);
|
||||
conn = smb2_oplock_break_conn_get(opinfo, ci);
|
||||
if (!conn)
|
||||
return ksmbd_invalidate_durable_fd(opinfo->fid);
|
||||
|
||||
work = ksmbd_alloc_work_struct();
|
||||
if (!work)
|
||||
if (!work) {
|
||||
ksmbd_conn_put(conn);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP);
|
||||
if (!br_info) {
|
||||
ksmbd_free_work_struct(work);
|
||||
ksmbd_conn_put(conn);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
|
@ -957,7 +995,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
|
|||
br_info->open_trunc = opinfo->open_trunc;
|
||||
|
||||
work->request_buf = (char *)br_info;
|
||||
work->conn = ksmbd_conn_get(conn);
|
||||
/* Transfer the reference acquired by smb2_oplock_break_conn_get(). */
|
||||
work->conn = conn;
|
||||
work->sess = opinfo->sess;
|
||||
|
||||
ksmbd_conn_r_count_inc(conn);
|
||||
|
|
@ -1154,9 +1193,9 @@ static void wait_lease_breaking(struct oplock_info *opinfo)
|
|||
}
|
||||
}
|
||||
|
||||
static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
|
||||
struct ksmbd_work *in_work, bool share_break,
|
||||
bool sync_lease_break)
|
||||
static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
|
||||
int req_op_level, struct ksmbd_work *in_work,
|
||||
bool share_break, bool sync_lease_break)
|
||||
{
|
||||
int err = 0;
|
||||
bool sent_interim = false;
|
||||
|
|
@ -1298,7 +1337,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
|
|||
}
|
||||
}
|
||||
|
||||
err = smb2_oplock_break_noti(brk_opinfo);
|
||||
err = smb2_oplock_break_noti(brk_opinfo, ci);
|
||||
|
||||
ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
|
||||
if (brk_opinfo->op_state == OPLOCK_CLOSING)
|
||||
|
|
@ -1326,13 +1365,14 @@ static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void oplock_break_drain_none(struct list_head *head)
|
||||
static void oplock_break_drain_none(struct list_head *head,
|
||||
struct ksmbd_inode *ci)
|
||||
{
|
||||
struct oplock_break_entry *ent, *tmp;
|
||||
|
||||
list_for_each_entry_safe(ent, tmp, head, list) {
|
||||
oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false,
|
||||
false);
|
||||
oplock_break(ent->opinfo, ci, SMB2_OPLOCK_LEVEL_NONE, NULL,
|
||||
false, false);
|
||||
list_del(&ent->list);
|
||||
opinfo_put(ent->opinfo);
|
||||
kfree(ent);
|
||||
|
|
@ -1481,7 +1521,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
|
|||
}
|
||||
up_read(&p_ci->m_lock);
|
||||
|
||||
oplock_break_drain_none(&brk_list);
|
||||
oplock_break_drain_none(&brk_list, p_ci);
|
||||
|
||||
ksmbd_inode_put(p_ci);
|
||||
}
|
||||
|
|
@ -1525,7 +1565,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
|
|||
}
|
||||
up_read(&p_ci->m_lock);
|
||||
|
||||
oplock_break_drain_none(&brk_list);
|
||||
oplock_break_drain_none(&brk_list, p_ci);
|
||||
|
||||
ksmbd_inode_put(p_ci);
|
||||
}
|
||||
|
|
@ -1665,7 +1705,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
|
|||
prev_durable_detached = prev_op_snapshot.durable_detached;
|
||||
prev_fid = prev_op_snapshot.fid;
|
||||
|
||||
err = oplock_break(prev_opinfo, break_level, work,
|
||||
err = oplock_break(prev_opinfo, ci, break_level, work,
|
||||
share_ret < 0 && prev_opinfo->is_lease, false);
|
||||
if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
|
||||
ksmbd_invalidate_durable_fd(prev_fid);
|
||||
|
|
@ -1771,7 +1811,8 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
|
|||
}
|
||||
|
||||
brk_opinfo->open_trunc = is_trunc;
|
||||
oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work, false, false);
|
||||
oplock_break(brk_opinfo, fp->f_ci, SMB2_OPLOCK_LEVEL_II, work, false,
|
||||
false);
|
||||
sent_break = true;
|
||||
opinfo_put(brk_opinfo);
|
||||
|
||||
|
|
@ -1863,7 +1904,7 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
|
|||
brk_op->op_state = OPLOCK_STATE_NONE;
|
||||
spin_unlock(&brk_op->state_lock);
|
||||
} else {
|
||||
oplock_break(brk_op,
|
||||
oplock_break(brk_op, ci,
|
||||
brk_op->is_lease && !is_trunc ?
|
||||
SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
|
||||
send_interim && !sent_interim ? work : NULL,
|
||||
|
|
|
|||
|
|
@ -2790,6 +2790,7 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
struct ksmbd_session *sess = work->sess;
|
||||
char *treename = NULL, *name = NULL;
|
||||
struct ksmbd_tree_conn_status status;
|
||||
struct ksmbd_tree_connect *tree_conn = NULL;
|
||||
struct ksmbd_share_config *share = NULL;
|
||||
int rc = -EINVAL;
|
||||
|
||||
|
|
@ -2817,6 +2818,7 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
|
||||
status = ksmbd_tree_conn_connect(work, name);
|
||||
if (status.ret == KSMBD_TREE_CONN_STATUS_OK) {
|
||||
tree_conn = status.tree_conn;
|
||||
rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
|
||||
share = status.tree_conn->share_conf;
|
||||
|
||||
|
|
@ -2860,8 +2862,15 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
status.tree_conn->posix_extensions = true;
|
||||
|
||||
down_write(&sess->tree_conns_lock);
|
||||
status.tree_conn->t_state = TREE_CONNECTED;
|
||||
if (status.tree_conn->t_state == TREE_DISCONNECTED) {
|
||||
status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
|
||||
share = NULL;
|
||||
} else {
|
||||
status.tree_conn->t_state = TREE_CONNECTED;
|
||||
}
|
||||
up_write(&sess->tree_conns_lock);
|
||||
if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
|
||||
goto out_err1;
|
||||
rsp->StructureSize = cpu_to_le16(16);
|
||||
out_err1:
|
||||
/*
|
||||
|
|
@ -2888,9 +2897,6 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp));
|
||||
if (rc) {
|
||||
if (status.ret == KSMBD_TREE_CONN_STATUS_OK) {
|
||||
down_write(&sess->tree_conns_lock);
|
||||
status.tree_conn->t_state = TREE_DISCONNECTED;
|
||||
up_write(&sess->tree_conns_lock);
|
||||
ksmbd_tree_conn_disconnect(sess, status.tree_conn);
|
||||
status.tree_conn = NULL;
|
||||
}
|
||||
|
|
@ -2931,6 +2937,9 @@ int smb2_tree_connect(struct ksmbd_work *work)
|
|||
if (status.ret != KSMBD_TREE_CONN_STATUS_OK)
|
||||
smb2_set_err_rsp(work);
|
||||
|
||||
if (tree_conn)
|
||||
ksmbd_tree_connect_put(tree_conn);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
@ -3034,17 +3043,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work)
|
|||
|
||||
ksmbd_close_tree_conn_fds(work);
|
||||
|
||||
down_write(&sess->tree_conns_lock);
|
||||
if (tcon->t_state == TREE_DISCONNECTED) {
|
||||
up_write(&sess->tree_conns_lock);
|
||||
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
|
||||
err = -ENOENT;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
tcon->t_state = TREE_DISCONNECTED;
|
||||
up_write(&sess->tree_conns_lock);
|
||||
|
||||
err = ksmbd_tree_conn_disconnect(sess, tcon);
|
||||
if (err) {
|
||||
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
|
||||
|
|
|
|||
|
|
@ -2007,6 +2007,11 @@ static ssize_t ksmbd_vfs_copy_file_range_buffered(struct ksmbd_work *work,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool ksmbd_vfs_copy_range_valid(loff_t offset, size_t len)
|
||||
{
|
||||
return offset >= 0 && (loff_t)len <= MAX_LFS_FILESIZE - offset;
|
||||
}
|
||||
|
||||
int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
|
||||
struct ksmbd_file *src_fp,
|
||||
struct ksmbd_file *dst_fp,
|
||||
|
|
@ -2042,6 +2047,10 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
|
|||
dst_off = le64_to_cpu(chunks[i].TargetOffset);
|
||||
len = le32_to_cpu(chunks[i].Length);
|
||||
|
||||
if (!ksmbd_vfs_copy_range_valid(src_off, len) ||
|
||||
!ksmbd_vfs_copy_range_valid(dst_off, len))
|
||||
return -E2BIG;
|
||||
|
||||
if (check_lock_range(src_fp->filp, src_off,
|
||||
src_off + len - 1, READ))
|
||||
return -EAGAIN;
|
||||
|
|
@ -2134,7 +2143,8 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
|
|||
len = le32_to_cpu(chunks[i].Length);
|
||||
copy_len = len;
|
||||
|
||||
if (src_off < 0)
|
||||
if (!ksmbd_vfs_copy_range_valid(src_off, len) ||
|
||||
!ksmbd_vfs_copy_range_valid(dst_off, len))
|
||||
return -E2BIG;
|
||||
|
||||
if (src_off > src_file_size || len > src_file_size - src_off) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user