Skip to contentSkip to conversation

FAILED: patch "[PATCH] ksmbd: fix app-instance non-durable supersede session UAF" failed to apply to 7.3-stable tree

PATCH New [email protected] 2 participants 5 messages Nov 3

Subsystems (2)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 7.3-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.3.y
git checkout FETCH_HEAD
git cherry-pick -x f728cfee6929057087c303d07d7a6a7c61196b48
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026110310-spool-turban-3d3a@gregkh' --subject-prefix 'PATCH 7.3.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From f728cfee6929057087c303d07d7a6a7c61196b48 Mon Sep 17 00:00:00 2001
From: Ian Bridges <[email protected]>
Date: Mon, 26 Oct 2026 00:34:22 -0500
Subject: [PATCH] ksmbd: fix app-instance non-durable supersede session UAF

ksmbd_close_fd_app_instance_id() supersedes a prior open that has the same AppInstanceId. It reaches that open's session through opinfo->sess. This pointer is not refcounted.

Session teardown detaches durable handles before it frees the session. It does not detach a non-durable open. That open still points at the session through opinfo->sess after the session is freed. Another session can supersede the open at the same time. The supersede then takes a write lock through the stale pointer, in the freed session's file table.

  BUG: KASAN: slab-use-after-free in _raw_write_lock+0x71/0xd0
  Call Trace:
   _raw_write_lock+0x71/0xd0
   ksmbd_close_fd_app_instance_id+0xd9/0x670 [ksmbd]
   smb2_open+0x3e10/0xd910 [ksmbd]
  Freed by task 212:
   ksmbd_session_destroy+0x38d/0x480 [ksmbd]

Fix this by looking the session up by opinfo->session_id. The lookup returns a reference, as smb2_break_collect_channels() does. Recheck that opinfo->sess still points at it before using it.

Do the lookup without m_lock held. destroy_previous_session() takes m_lock under the session table lock, and taking them in the other order here would deadlock.

Fixes 16c30649709d ("ksmbd: handle durable v2 app instance id")
Cc [email protected] # e0386cd6498b: ksmbd: validate AppInstanceId takeover target
fs/smb/server/vfs_cache.c +30 -2
index 7a0f13277ec0..4368e069fd5e 100644
@@ -1262,7 +1262,9 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 				 struct ksmbd_file *fp, bool *closed)
 {
 	struct ksmbd_file_table *ft;
+	struct ksmbd_session *sess;
 	struct oplock_info *opinfo;
+	u64 session_id;
 	int n_to_drop = 0;
 
 	*closed = false;
@@ -1275,10 +1277,35 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 		up_read(&fp->f_ci->m_lock);
 		goto out;
 	}
+	/*
+	 * opinfo->sess is not refcounted, and m_lock does not keep it alive.
+	 * The owner can be torn down while we hold only a reference to fp.
+	 * Look the owner up by id to take a real reference instead.
+	 */
+	session_id = opinfo->session_id;
+	up_read(&fp->f_ci->m_lock);
 
-	ft = &opinfo->sess->file_table;
+	/* destroy_previous_session() takes m_lock under sessions_table_lock. */
+	sess = ksmbd_session_lookup_slowpath(session_id);
+	if (!sess)
+		goto out;
+
+	down_read(&fp->f_ci->m_lock);
+	if (opinfo->sess != sess) {
+		up_read(&fp->f_ci->m_lock);
+		ksmbd_user_session_put(sess);
+		goto out;
+	}
+	ft = &sess->file_table;
 	write_lock(&ft->lock);
-	if (fp->f_state == FP_INITED && has_file_id(fp->volatile_id) &&
+	/*
+	 * The table may already be detached by destroy_previous_session().
+	 * A new session could also reuse the old id and address and pass the
+	 * check above. Only remove fp if this table still publishes it.
+	 */
+	if (ft->idr && fp->f_state == FP_INITED &&
+	    has_file_id(fp->volatile_id) &&
+	    idr_find(ft->idr, fp->volatile_id) == fp &&
 	    ksmbd_app_instance_id_matches(work, fp)) {
 		idr_remove(ft->idr, fp->volatile_id);
 		fp->volatile_id = KSMBD_NO_FID;
@@ -1286,6 +1313,7 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 	}
 	write_unlock(&ft->lock);
 	up_read(&fp->f_ci->m_lock);
+	ksmbd_user_session_put(sess);
 	opinfo_put(opinfo);
 	opinfo = NULL;
 
maintainer
#1 lore raw

PATCH 1/4 ksmbd: use clear_and_wake_up_bit() directly

From: Daijiamu <[email protected]>

[ Upstream commit 8852052e32df965a76daecbd8cda0ecf950b54df ]

clear_and_wake_up_bit() was introduced by commit 8236b0ae31c8 ("bdi:
wake up concurrent wb_shutdown() callers.") to replace the open-coded clear_bit_unlock() + smp_mb__after_atomic() + wake_up_bit() sequence.

Replace the sequence and drop the now-trivial wake_up_oplock_break() wrapper, calling the helper directly at each call site.

Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")

---
 fs/smb/server/oplock.c | 20 ++++++--------------
 1 file changed, 6 insertions(+), 14 deletions(-)
fs/smb/server/oplock.c +6 -14
index 1b8c3482d1e4e..25ff8408f0ba2 100644
@@ -763,14 +763,6 @@ static bool wait_for_break_ack(struct oplock_info *opinfo)
 	return false;
 }
 
-static void wake_up_oplock_break(struct oplock_info *opinfo)
-{
-	clear_bit_unlock(0, &opinfo->pending_break);
-	/* memory barrier is needed for wake_up_bit() */
-	smp_mb__after_atomic();
-	wake_up_bit(&opinfo->pending_break, 0);
-}
-
 static bool oplock_break_set_ack_wait(struct oplock_info *opinfo)
 {
 	bool ret = false;
@@ -826,13 +818,13 @@ static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
 
 	if (opinfo->level <= req_op_level) {
 		if (opinfo->is_lease == false) {
-			wake_up_oplock_break(opinfo);
+			clear_and_wake_up_bit(0, &opinfo->pending_break);
 			return 1;
 		}
 		if (opinfo->o_lease->state !=
 		    (SMB2_LEASE_HANDLE_CACHING_LE |
 		     SMB2_LEASE_READ_CACHING_LE)) {
-			wake_up_oplock_break(opinfo);
+			clear_and_wake_up_bit(0, &opinfo->pending_break);
 			return 1;
 		}
 	}
@@ -1260,7 +1252,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
 				SMB2_LEASE_HANDLE_CACHING_LE)) {
 			if (!oplock_break_set_ack_wait(brk_opinfo)) {
 				atomic_dec_if_positive(&brk_opinfo->breaking_cnt);
-				wake_up_oplock_break(brk_opinfo);
+				clear_and_wake_up_bit(0, &brk_opinfo->pending_break);
 				return -ENOENT;
 			}
 		} else
@@ -1309,7 +1301,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
 		      lease->state != SMB2_LEASE_NONE_LE)))
 			goto again;
 
-		wake_up_oplock_break(brk_opinfo);
+		clear_and_wake_up_bit(0, &brk_opinfo->pending_break);
 		return err;
 	} else {
 		err = oplock_break_pending(brk_opinfo, req_op_level);
@@ -1319,7 +1311,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
 		if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
 		    brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
 			if (!oplock_break_set_ack_wait(brk_opinfo)) {
-				wake_up_oplock_break(brk_opinfo);
+				clear_and_wake_up_bit(0, &brk_opinfo->pending_break);
 				return -ENOENT;
 			}
 		}
@@ -1342,7 +1334,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,
 	ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
 	if (brk_opinfo->op_state == OPLOCK_CLOSING)
 		err = -EAGAIN;
-	wake_up_oplock_break(brk_opinfo);
+	clear_and_wake_up_bit(0, &brk_opinfo->pending_break);
 
 	return err;
 }
Signature · 1 line
2.53.0
maintainer
#2 lore raw

PATCH 2/4 ksmbd: track delivery of queued TCP writes

From: Namjae Jeon <[email protected]>

[ Upstream commit c0632d2010ffc006eed9becc8d0d10d54f294bbd ]

A nonblocking break send must distinguish a PDU accepted by TCP from one still queued in ksmbd. Retrying an accepted PDU can send duplicates, while abandoning a partial send can corrupt the stream.

Queue owned PDU bytes when TCP cannot accept the full message. Track the stream position through socket delivery and TCP ACK, and expose it to break retries. Bound retained data and stop queued writes during teardown.

Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")

---
 fs/smb/server/connection.c    |  52 +++++-
 fs/smb/server/connection.h    |  24 ++-
 fs/smb/server/transport_tcp.c | 299 +++++++++++++++++++++++++++++++++-
 3 files changed, 356 insertions(+), 19 deletions(-)
fs/smb/server/connection.c +44 -8
index 52e2892c58b4d..b519376ecc17b 100644
@@ -458,17 +458,13 @@ void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)
 
 void ksmbd_conn_abort(struct ksmbd_conn *conn)
 {
-	bool shutdown = false;
-
 	spin_lock(&conn->request_lock);
-	if (!ksmbd_conn_exiting(conn) && !ksmbd_conn_releasing(conn)) {
+	if (!ksmbd_conn_exiting(conn) && !ksmbd_conn_releasing(conn))
 		ksmbd_conn_set_exiting(conn);
-		shutdown = true;
-	}
 	spin_unlock(&conn->request_lock);
 	wake_up_all(&conn->req_running_q);
 
-	if (shutdown && conn->transport->ops->shutdown)
+	if (conn->transport->ops->shutdown)
 		conn->transport->ops->shutdown(conn->transport);
 }
 
@@ -535,12 +531,19 @@ static int __ksmbd_conn_write(struct ksmbd_work *work,
 	tx->need_invalidate_rkey = work->need_invalidate_rkey;
 	tx->remote_key = work->remote_key;
 
-	ksmbd_conn_lock(conn);
+	if (tx->tracking) {
+		/* A busy writer must not consume the break's retry budget. */
+		if (!mutex_trylock(&conn->srv_mutex))
+			return -EAGAIN;
+	} else {
+		ksmbd_conn_lock(conn);
+	}
 	sent = conn->transport->ops->writev(conn->transport, tx);
 	ksmbd_conn_unlock(conn);
 
 	if (sent < 0) {
-		pr_err("Failed to send message: %d\n", sent);
+		if (!tx->tracking || sent != -EAGAIN)
+			pr_err("Failed to send message: %d\n", sent);
 		return sent;
 	}
 
@@ -554,6 +557,20 @@ int ksmbd_conn_write(struct ksmbd_work *work)
 	return __ksmbd_conn_write(work, &tx);
 }
 
+int ksmbd_conn_write_tracked(struct ksmbd_work *work,
+			     struct ksmbd_tx_tracking *tracking)
+{
+	struct ksmbd_transport_write tx = {};
+
+	tracking->valid = false;
+	/* Transports without delivery tracking keep their synchronous send path. */
+	if (work->conn->transport->ops->is_acked) {
+		tx.msg_flags = MSG_DONTWAIT;
+		tx.tracking = tracking;
+	}
+	return __ksmbd_conn_write(work, &tx);
+}
+
 int ksmbd_conn_write_eor(struct ksmbd_work *work)
 {
 	struct ksmbd_transport_write tx = {
@@ -807,7 +824,12 @@ int ksmbd_conn_handler_loop(void *p)
 		}
 	}
 
+	/* Exclude new break senders before draining their work references. */
+	spin_lock(&conn->request_lock);
 	ksmbd_conn_set_releasing(conn);
+	spin_unlock(&conn->request_lock);
+	/* TCP writers waiting for queue space must release their work references. */
+	wake_up_all(&conn->req_running_q);
 	ksmbd_conn_cancel_async_requests(conn);
 	/* Wait till all reference dropped to the Server object*/
 	ksmbd_debug(CONN, "Wait for all pending requests(%d)\n", atomic_read(&conn->r_count));
@@ -834,6 +856,20 @@ void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn)
 	atomic_inc(&conn->r_count);
 }
 
+/* The caller already owns a connection reference. */
+bool ksmbd_conn_r_count_inc_if_good(struct ksmbd_conn *conn)
+{
+	bool ret = false;
+
+	spin_lock(&conn->request_lock);
+	if (ksmbd_conn_good(conn)) {
+		atomic_inc(&conn->r_count);
+		ret = true;
+	}
+	spin_unlock(&conn->request_lock);
+	return ret;
+}
+
 void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn)
 {
 	/*
fs/smb/server/connection.h +22 -2
index 8671ca6fe1267..a4f5a64b36b4e 100644
@@ -102,6 +102,8 @@ struct ksmbd_conn {
 	wait_queue_head_t		r_count_q;
 	/* Lock to protect requests list*/
 	spinlock_t			request_lock;
+	/* Set under request_lock at teardown start and kept set thereafter. */
+	bool				releasing;
 	struct list_head		requests;
 	struct list_head		async_requests;
 	int				connection_type;
@@ -161,6 +163,13 @@ struct ksmbd_conn_ops {
 	int	(*terminate_fn)(struct ksmbd_conn *conn);
 };
 
+struct ksmbd_tx_tracking {
+	/* Required TCP byte count, including transport and socket queues. */
+	u64		end;
+	unsigned long	retry_after;
+	bool		valid;
+};
+
 struct ksmbd_transport_write {
 	struct kvec	*iov;
 	int		iov_cnt;
@@ -168,6 +177,7 @@ struct ksmbd_transport_write {
 	bool		need_invalidate_rkey;
 	unsigned int	remote_key;
 	int		msg_flags;
+	struct ksmbd_tx_tracking *tracking;
 };
 
 struct ksmbd_transport_ops {
@@ -175,8 +185,12 @@ struct ksmbd_transport_ops {
 	void (*shutdown)(struct ksmbd_transport *t);
 	int (*read)(struct ksmbd_transport *t, char *buf,
 		    unsigned int size, int max_retries);
+	/* Retained bytes must be privately owned before a full result is returned. */
 	int (*writev)(struct ksmbd_transport *t,
 		      const struct ksmbd_transport_write *tx);
+	/* Optional delivery tracking for nonblocking break sends. */
+	bool (*is_acked)(struct ksmbd_transport *t,
+			 const struct ksmbd_tx_tracking *tracking);
 	int (*rdma_read)(struct ksmbd_transport *t,
 			 void *buf, unsigned int len,
 			 struct smbdirect_buffer_descriptor_v1 *desc,
@@ -216,6 +230,8 @@ int ksmbd_conn_wq_init(void);
 void ksmbd_conn_wq_destroy(void);
 bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
 int ksmbd_conn_write(struct ksmbd_work *work);
+int ksmbd_conn_write_tracked(struct ksmbd_work *work,
+			     struct ksmbd_tx_tracking *tracking);
 int ksmbd_conn_write_eor(struct ksmbd_work *work);
 int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,
 			 void *buf, unsigned int buflen,
@@ -234,6 +250,7 @@ void ksmbd_conn_transport_destroy(void);
 void ksmbd_conn_lock(struct ksmbd_conn *conn);
 void ksmbd_conn_unlock(struct ksmbd_conn *conn);
 void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn);
+bool ksmbd_conn_r_count_inc_if_good(struct ksmbd_conn *conn);
 void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn);
 
 /*
@@ -249,7 +266,8 @@ static inline bool ksmbd_conn_new(struct ksmbd_conn *conn)
 
 static inline bool ksmbd_conn_good(struct ksmbd_conn *conn)
 {
-	return READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
+	return !READ_ONCE(conn->releasing) &&
+		READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
 }
 
 static inline unsigned int
@@ -283,7 +301,8 @@ static inline bool ksmbd_conn_exiting(struct ksmbd_conn *conn)
 
 static inline bool ksmbd_conn_releasing(struct ksmbd_conn *conn)
 {
-	return READ_ONCE(conn->status) == KSMBD_SESS_RELEASING;
+	return READ_ONCE(conn->releasing) ||
+		READ_ONCE(conn->status) == KSMBD_SESS_RELEASING;
 }
 
 static inline void ksmbd_conn_set_new(struct ksmbd_conn *conn)
@@ -318,6 +337,7 @@ static inline void ksmbd_conn_set_exiting(struct ksmbd_conn *conn)
 
 static inline void ksmbd_conn_set_releasing(struct ksmbd_conn *conn)
 {
+	WRITE_ONCE(conn->releasing, true);
 	WRITE_ONCE(conn->status, KSMBD_SESS_RELEASING);
 }
 
fs/smb/server/transport_tcp.c +290 -9

373 lines not on this page · raw message

Signature · 1 line
2.53.0
maintainer
#3 lore raw

PATCH 3/4 ksmbd: retry break notifications on another connection

In reply to Sasha Levin

From: Namjae Jeon <[email protected]>

[ Upstream commit 14ad1f21fbf05179116dd6ef4ab3b6556478fa50 ]

An oplock or lease break belongs to an open that can outlive its original channel. A draining channel or stalled TCP writer can otherwise leave the break pending until timeout or invalidate a durable open.

Snapshot the owner's live channels and try eligible connections with the same notification. Track TCP delivery separately from the SMB ACK, retry when a channel stops progressing, and share one break deadline. Run ordinary break waits on a dedicated workqueue so blocked request responses do not stall breaks.

Publish NONE before sending a level II break, which requires no ACK, so an unsolicited ACK observes the completed level while TCP delivery is pending.

Keep the first rename lease notification in the request context and enqueue the interim writer only after the notification is accepted. Join that work before returning so a blocked interim response does not stop notification retries or release the request early.

Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")

---
 fs/smb/server/ksmbd_work.c        |  19 +
 fs/smb/server/ksmbd_work.h        |   2 +
 fs/smb/server/mgmt/user_session.c |  31 ++
 fs/smb/server/mgmt/user_session.h |   3 +
 fs/smb/server/oplock.c            | 855 +++++++++++++++++-------------
 fs/smb/server/oplock.h            |  14 +-
 fs/smb/server/vfs_cache.c         |   2 +
 7 files changed, 539 insertions(+), 387 deletions(-)
fs/smb/server/ksmbd_work.c +19 -0
index d307aefe0aec1..b0285eb02f35c 100644
@@ -16,6 +16,7 @@
 
 static struct kmem_cache *work_cache;
 static struct workqueue_struct *ksmbd_wq;
+static struct workqueue_struct *ksmbd_break_wq;
 
 static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
 {
@@ -113,6 +114,12 @@ int ksmbd_workqueue_init(void)
 	ksmbd_wq = alloc_workqueue("ksmbd-io", WQ_PERCPU, 0);
 	if (!ksmbd_wq)
 		return -ENOMEM;
+	ksmbd_break_wq = alloc_workqueue("ksmbd-break", WQ_UNBOUND, 0);
+	if (!ksmbd_break_wq) {
+		destroy_workqueue(ksmbd_wq);
+		ksmbd_wq = NULL;
+		return -ENOMEM;
+	}
 	return 0;
 }
 
@@ -120,6 +127,8 @@ void ksmbd_workqueue_destroy(void)
 {
 	destroy_workqueue(ksmbd_wq);
 	ksmbd_wq = NULL;
+	destroy_workqueue(ksmbd_break_wq);
+	ksmbd_break_wq = NULL;
 }
 
 bool ksmbd_queue_work(struct ksmbd_work *work)
@@ -127,6 +136,16 @@ bool ksmbd_queue_work(struct ksmbd_work *work)
 	return queue_work(ksmbd_wq, &work->work);
 }
 
+void ksmbd_queue_io_work(struct work_struct *work)
+{
+	queue_work(ksmbd_wq, work);
+}
+
+void ksmbd_queue_break_work(struct work_struct *work)
+{
+	queue_work(ksmbd_break_wq, work);
+}
+
 static inline void __ksmbd_iov_pin(struct ksmbd_work *work, void *ib,
 				   unsigned int ib_len)
 {
fs/smb/server/ksmbd_work.h +2 -0
index 0844aa929f55d..6877f68381186 100644
@@ -156,6 +156,8 @@ int ksmbd_work_pool_init(void);
 int ksmbd_workqueue_init(void);
 void ksmbd_workqueue_destroy(void);
 bool ksmbd_queue_work(struct ksmbd_work *work);
+void ksmbd_queue_io_work(struct work_struct *work);
+void ksmbd_queue_break_work(struct work_struct *work);
 int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
 			   void *aux_buf, unsigned int aux_size);
 int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
fs/smb/server/mgmt/user_session.c +31 -0
index 63d455053cdd2..1b30e432ebfd8 100644
@@ -767,6 +767,37 @@ struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)
 	return sess;
 }
 
+/* The caller holds a session reference and must put each returned connection. */
+unsigned int ksmbd_session_get_channels(struct ksmbd_session *sess,
+					struct ksmbd_conn **conns,
+					unsigned int max_conns)
+{
+	struct channel *chann;
+	unsigned long index;
+	unsigned int nr_conns = 0;
+
+	/*
+	 * Channel connections are borrowed. Unregister can leave the channel
+	 * list in a pinned session after removing it from the registry, so
+	 * chann_lock alone does not keep those connections alive. Only collect
+	 * registered sessions, excluding both unregister and connection cleanup
+	 * until the connection references have been taken.
+	 */
+	down_read(&sessions_table_lock);
+	if (hlist_unhashed(&sess->hlist))
+		goto out;
+	down_read(&sess->chann_lock);
+	xa_for_each(&sess->ksmbd_chann_list, index, chann) {
+		if (nr_conns == max_conns)
+			break;
+		conns[nr_conns++] = ksmbd_conn_get(chann->conn);
+	}
+	up_read(&sess->chann_lock);
+out:
+	up_read(&sessions_table_lock);
+	return nr_conns;
+}
+
 struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn,
 						      unsigned long long id)
 {
fs/smb/server/mgmt/user_session.h +3 -0
index ef52d19e58977..8e4ddb04f0e6d 100644
@@ -99,6 +99,9 @@ void ksmbd_session_release_slot(struct ksmbd_conn *conn);
 void ksmbd_session_destroy(struct ksmbd_session *sess);
 
 struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id);
+unsigned int ksmbd_session_get_channels(struct ksmbd_session *sess,
+					struct ksmbd_conn **conns,
+					unsigned int max_conns);
 struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
 					   unsigned long long id);
 bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
fs/smb/server/oplock.c +481 -374

1,057 lines not on this page · raw message

fs/smb/server/oplock.h +1 -13
index b08d21758e07a..e114e2148c30e 100644
@@ -62,6 +62,7 @@ struct lease {
 struct oplock_info {
 	struct ksmbd_conn	*conn;
 	struct ksmbd_session	*sess;
+	u64			session_id;
 	struct ksmbd_work	*work;
 	struct ksmbd_file	*o_fp;
 	int                     level;
@@ -82,19 +83,6 @@ struct oplock_info {
 	struct rcu_head		rcu;
 };
 
-struct lease_break_info {
-	__le32			curr_state;
-	__le32			new_state;
-	__le16			epoch;
-	char			lease_key[SMB2_LEASE_KEY_SIZE];
-};
-
-struct oplock_break_info {
-	int level;
-	int open_trunc;
-	int fid;
-};
-
 int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
 		     u64 pid, struct ksmbd_file *fp, __u16 tid,
 		     struct lease_ctx_info *lctx, int share_ret, bool replay);
fs/smb/server/vfs_cache.c +2 -0
index d40196948efe7..a2051dfb47d91 100644
@@ -1815,6 +1815,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
 		ksmbd_conn_put(op->conn);
 		op->conn = NULL;
 		op->sess = NULL;
+		op->session_id = 0;
 	}
 	up_write(&ci->m_lock);
 
@@ -1995,6 +1996,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
 			continue;
 		op->conn = ksmbd_conn_get(fp->conn);
 		op->sess = work->sess;
+		op->session_id = work->sess->id;
 	}
 	up_write(&ci->m_lock);
 
Signature · 1 line
2.53.0
maintainer
#4 lore raw

PATCH 4/4 ksmbd: fix app-instance non-durable supersede session UAF

In reply to Sasha Levin

From: Ian Bridges <[email protected]>

[ Upstream commit f728cfee6929057087c303d07d7a6a7c61196b48 ]

ksmbd_close_fd_app_instance_id() supersedes a prior open that has the same AppInstanceId. It reaches that open's session through opinfo->sess. This pointer is not refcounted.

Session teardown detaches durable handles before it frees the session. It does not detach a non-durable open. That open still points at the session through opinfo->sess after the session is freed. Another session can supersede the open at the same time. The supersede then takes a write lock through the stale pointer, in the freed session's file table.

  BUG: KASAN: slab-use-after-free in _raw_write_lock+0x71/0xd0
  Call Trace:
   _raw_write_lock+0x71/0xd0
   ksmbd_close_fd_app_instance_id+0xd9/0x670 [ksmbd]
   smb2_open+0x3e10/0xd910 [ksmbd]
  Freed by task 212:
   ksmbd_session_destroy+0x38d/0x480 [ksmbd]

Fix this by looking the session up by opinfo->session_id. The lookup returns a reference, as smb2_break_collect_channels() does. Recheck that opinfo->sess still points at it before using it.

Do the lookup without m_lock held. destroy_previous_session() takes m_lock under the session table lock, and taking them in the other order here would deadlock.

Fixes 16c30649709d ("ksmbd: handle durable v2 app instance id")
Cc [email protected] # e0386cd6498b: ksmbd: validate AppInstanceId takeover target
---
 fs/smb/server/vfs_cache.c | 32 ++++++++++++++++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)
fs/smb/server/vfs_cache.c +30 -2
index a2051dfb47d91..5f1442c103d81 100644
@@ -1054,7 +1054,9 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 				 struct ksmbd_file *fp, bool *closed)
 {
 	struct ksmbd_file_table *ft;
+	struct ksmbd_session *sess;
 	struct oplock_info *opinfo;
+	u64 session_id;
 	int n_to_drop = 0;
 
 	*closed = false;
@@ -1067,10 +1069,35 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 		up_read(&fp->f_ci->m_lock);
 		goto out;
 	}
+	/*
+	 * opinfo->sess is not refcounted, and m_lock does not keep it alive.
+	 * The owner can be torn down while we hold only a reference to fp.
+	 * Look the owner up by id to take a real reference instead.
+	 */
+	session_id = opinfo->session_id;
+	up_read(&fp->f_ci->m_lock);
 
-	ft = &opinfo->sess->file_table;
+	/* destroy_previous_session() takes m_lock under sessions_table_lock. */
+	sess = ksmbd_session_lookup_slowpath(session_id);
+	if (!sess)
+		goto out;
+
+	down_read(&fp->f_ci->m_lock);
+	if (opinfo->sess != sess) {
+		up_read(&fp->f_ci->m_lock);
+		ksmbd_user_session_put(sess);
+		goto out;
+	}
+	ft = &sess->file_table;
 	write_lock(&ft->lock);
-	if (fp->f_state == FP_INITED && has_file_id(fp->volatile_id) &&
+	/*
+	 * The table may already be detached by destroy_previous_session().
+	 * A new session could also reuse the old id and address and pass the
+	 * check above. Only remove fp if this table still publishes it.
+	 */
+	if (ft->idr && fp->f_state == FP_INITED &&
+	    has_file_id(fp->volatile_id) &&
+	    idr_find(ft->idr, fp->volatile_id) == fp &&
 	    ksmbd_app_instance_id_matches(work, fp)) {
 		idr_remove(ft->idr, fp->volatile_id);
 		fp->volatile_id = KSMBD_NO_FID;
@@ -1078,6 +1105,7 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,
 	}
 	write_unlock(&ft->lock);
 	up_read(&fp->f_ci->m_lock);
+	ksmbd_user_session_put(sess);
 	opinfo_put(opinfo);
 	opinfo = NULL;
 
Signature · 1 line
2.53.0