summaryrefslogtreecommitdiff
path: root/fs/smb
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-09-11 11:51:26 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-09-11 11:51:26 +0200
commitffb45b46184f54bf84d95e82df46932294b2031a (patch)
treecc199e0ec572fb2bf65fa4eb144f0e562bb34687 /fs/smb
parent5eccd39d8efa3bc8d557be50f202bbf023837eed (diff)
parenta300e35c0a4b4a38fb53742ea6e2a203c98ee523 (diff)
downloadlinux-rolling-stable.tar.gz
linux-rolling-stable.zip
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/smb')
-rw-r--r--fs/smb/server/oplock.c79
-rw-r--r--fs/smb/server/smb2pdu.c7
2 files changed, 65 insertions, 21 deletions
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 79787099afdc..32f2f8b219ba 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -878,31 +878,69 @@ out:
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;
}
@@ -911,7 +949,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);
@@ -1074,9 +1113,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)
-{
+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;
@@ -1196,7 +1235,7 @@ again:
brk_opinfo->op_state = OPLOCK_ACK_WAIT;
}
- 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)
@@ -1224,13 +1263,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);
- list_del(&ent->list);
+ oplock_break(ent->opinfo, ci, SMB2_OPLOCK_LEVEL_NONE, NULL,
+ false, false); list_del(&ent->list);
opinfo_put(ent->opinfo);
kfree(ent);
}
@@ -1377,7 +1417,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);
}
@@ -1421,7 +1461,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);
}
@@ -1541,9 +1581,8 @@ 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,
- share_ret < 0 && prev_opinfo->is_lease);
- if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
+ 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);
opinfo_put(prev_opinfo);
if (err == -EAGAIN) {
@@ -1640,8 +1679,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);
- sent_break = true;
+ oplock_break(brk_opinfo, fp->f_ci, SMB2_OPLOCK_LEVEL_II, work, false,
+ false); sent_break = true;
opinfo_put(brk_opinfo);
return sent_break;
@@ -1727,11 +1766,11 @@ next:
brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
brk_op->op_state = OPLOCK_STATE_NONE;
} 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,
- false);
+ false, false);
}
sent_interim = true;
list_del(&ent->list);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 76f63f9adc72..e02e254645e2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -7226,13 +7226,18 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
}
aux_payload_buf =
- kvmalloc(rpc_resp->payload_sz, KSMBD_DEFAULT_GFP);
+ kvmalloc(ALIGN(rpc_resp->payload_sz, 8),
+ KSMBD_DEFAULT_GFP);
if (!aux_payload_buf) {
err = -ENOMEM;
goto out;
}
memcpy(aux_payload_buf, rpc_resp->payload, rpc_resp->payload_sz);
+ if (rpc_resp->payload_sz & 7)
+ memset(aux_payload_buf + rpc_resp->payload_sz, 0,
+ ALIGN(rpc_resp->payload_sz, 8) -
+ rpc_resp->payload_sz);
nbytes = rpc_resp->payload_sz;
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,