From bjacke at samba.org Thu Oct 1 20:21:53 2026 From: bjacke at samba.org (=?iso-8859-1?Q?Bj=F6rn?= JACKE) Date: Thu, 1 Oct 2026 22:21:53 +0200 Subject: [Announce] Samba 4.23.13 Available for Download Message-ID: Release Announcements --------------------- This is the latest stable release of the Samba 4.23 release series. There will be security releases of Samba 4.23 published only beyond this point. Also with the recently published 4.25 release, Samba 4.22 is EOL now. Changes since 4.23.12 --------------------- o Ralph Boehme * BUG 16097: POSIX ACL backend silently discards errors when processing NT ACLs with non-canonical ordering o Stefan Metzmacher * BUG 15962: SMB3 SESSION SETUP responses must always be signed * BUG 16081: winbindd_child_msg_filter: talloc_get_type_abort crashes when winbind max domain connections > 1 o Kopylov Pavel * BUG 16239: Fix parsing of uppercase "0X" hex values in the "kdc default domain supported enctypes" smb.conf parameter. o Anoop C S * BUG 16240: smbd temporary mkdir name can exceed NAME_MAX for otherwise valid client names * BUG 16258: Python smbconf transaction_start() lacks a talloc stack frame with CTDB o Martin Schwenke * BUG 16254: CTDB database operations can be run against incorrect nodes ####################################### Reporting bugs & Development Discussion ####################################### Please discuss this release on the samba-technical mailing list or by joining the #samba-technical:matrix.org matrix room, or #samba-technical IRC channel on irc.libera.chat. If you do report problems then please try to send high quality feedback. If you don't provide vital information to help us track down the problem then you will probably be ignored. All bug reports should be filed under the Samba 4.1 and newer product in the project's Bugzilla database (https://bugzilla.samba.org/). ====================================================================== == Our Code, Our Bugs, Our Responsibility. == The Samba Team ====================================================================== ================ Download Details ================ The uncompressed tarballs and patch files have been signed using GnuPG (ID AA99442FB680B620). The source code can be downloaded from: https://download.samba.org/pub/samba/stable/ The release notes are available online at: https://www.samba.org/samba/history/samba-4.23.13.html Our Code, Our Bugs, Our Responsibility. (https://bugzilla.samba.org/) --Enjoy The Samba Team -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: not available URL: From metze at samba.org Mon Oct 5 18:35:49 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:35:49 +0200 Subject: [PATCH 0/3] smb: smbdirect: fix listener backlog leak and teardown races Message-ID: Hi Namjae, These fix a set of problems in the shared smbdirect module (fs/smb/smbdirect/), reached through the listener/accept path, which in practice is driven by ksmbd for incoming SMB Direct connections. The main one is a pre-authentication remote denial of service: the listener keeps every accepted connection on a fixed-size backlog (listen.pending, limit 10 for ksmbd). A connection is only removed from that backlog on the success path (promotion to listen.ready after a completed negotiate, or dequeue by accept()). A connection that is accepted at the RDMA level but then fails before it is accepted by the upper layer - negotiate timeout, peer disconnect, or an invalid negotiate request - is never removed, so its backlog slot is leaked. After about ten such events the listener rejects every new connection with -EBUSY and SMB Direct stays unusable until the service is restarted. An unauthenticated peer can trigger this with ~10 aborted connections. This was reported by ???????? : https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ The series: - smbdirect_socket_destroy_sync() no longer waits for RDMA_CM_EVENT_DISCONNECTED after rdma_disconnect(). That wait could take very long (until the rdma cm gives up, e.g. a peer that just vanished) or never complete if the event already happened and the status was overwritten. rdma_destroy_id() in smbdirect_socket_destroy() is enough to stop further events; the rest of the disconnect protocol is handled by the rdma core asynchronously. This also makes releasing orphaned sockets (next patch) non-blocking. - A failed, not-yet-accepted child of a listener now moves itself to a new listen.orphaned list at the end of its cleanup work and queues listen.purge_orphaned_work, which releases it, so it no longer leaks its backlog slot (nor its struct sock / RDMA resources) for the lifetime of the listener. sc->accept.listener is only changed under listener->listen.lock, and whoever clears it owns the release; the listener is freed via kfree_rcu() so a child can dereference it under rcu_read_lock(). This is the DoS fix. - smbdirect_socket_accept() no longer hands out a socket that failed after it was put on the ready list (e.g. the peer disconnected in the meantime); it checks first_error / the status under listen.lock, orphans such a socket and tries the next one. I tested the reproducer and the problem is fixed and I run various xfstests. I think these are important and should go into 7.3 Stefan Metzmacher (3): smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() smb: smbdirect: release failed pending sockets of a listener smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() fs/smb/smbdirect/accept.c | 122 ++++++++++++++++++++++++++-------- fs/smb/smbdirect/connection.c | 12 ++-- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 116 ++++++++++++++++++++++++++++++-- fs/smb/smbdirect/socket.c | 113 +++++++++++++++++++++++++++---- fs/smb/smbdirect/socket.h | 37 +++++++++++ 6 files changed, 352 insertions(+), 50 deletions(-) -- 2.43.0 From metze at samba.org Mon Oct 5 18:35:50 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:35:50 +0200 Subject: [PATCH 1/3] smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() In-Reply-To: References: Message-ID: <22ab29ca3288e96cf92d4991c32b8975297d9a28.1791224972.git.metze@samba.org> smbdirect_socket_destroy_sync() waited for RDMA_CM_EVENT_DISCONNECTED after smbdirect_socket_cleanup_work() called rdma_disconnect(). That can take very long, e.g. if the peer just disappeared, until the rdma cm gives up, or forever if the event already happened, but the status was overwritten afterwards. This is not needed: smbdirect_socket_destroy() drains the qp under rdma_lock_handler(), destroys it and calls rdma_destroy_id(), which only waits for a currently running event handler and makes sure no further events are delivered. The rest of the disconnect protocol (DREQ/DREP and timewait for IB, abrupt close for iWarp) is handled by the rdma core asynchronously. drivers/nvme/host/rdma.c also just calls rdma_disconnect() and ib_drain_qp() before rdma_destroy_id(). So smbdirect_socket_destroy() now also accepts SMBDIRECT_SOCKET_DISCONNECTING and changes the status to SMBDIRECT_SOCKET_DISCONNECTED itself under rdma_lock_handler(). We could still get RDMA_CM_EVENT_DISCONNECTED in the small windows between rdma_unlock_handler() and rdma_destroy_id(), but in that case smbdirect_connection_rdma_event_handler() is basically a no-op. Fixes: 422a2436697d ("smb: smbdirect: introduce smbdirect_socket_destroy[_sync]()") Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Cc: linux-rdma at vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/connection.c | 12 ++++---- fs/smb/smbdirect/socket.c | 56 ++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index afd31fa12a36..f48c857bda5a 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -83,9 +83,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup[_status]() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. */ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL) smbdirect_socket_schedule_cleanup_status(sc, @@ -113,9 +113,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup_status() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. * * This is also a normal disconnect so * SMBDIRECT_LOG_INFO should be good enough diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index bb02df6158b9..c36cb7cc0088 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -511,7 +511,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->status == SMBDIRECT_SOCKET_DESTROYED) return; - WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED, + /* + * smbdirect_socket_destroy_sync() doesn't wait + * for RDMA_CM_EVENT_DISCONNECTED, so we may still + * be in SMBDIRECT_SOCKET_DISCONNECTING + * (or already reached SMBDIRECT_SOCKET_DISCONNECTED) + */ + WARN_ONCE(sc->status < SMBDIRECT_SOCKET_DISCONNECTING, "status=%s first_error=%1pe", smbdirect_socket_status_string(sc->status), SMBDIRECT_DEBUG_ERR_PTR(sc->first_error)); @@ -542,6 +548,32 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); + /* + * We hold the handler lock, so the rdma event + * handlers can't change the status anymore. + * + * If RDMA_CM_EVENT_DISCONNECTED didn't arrive yet, + * we just stop waiting for it here. + * + * We already disabled disconnect_work above + * and before we call rdma_unlock_handler below + * we call smbdirect_connection_destroy_qp which + * sets sc->ib.qp = NULL. + * + * Between rdma_unlock_handler() and + * rdma_destroy_id() below there's a small + * windows where RDMA_CM_EVENT_DISCONNECTED + * could still arrive. + * + * But smbdirect_connection_rdma_event_handler + * will be a noop when calling smbdirect_socket_schedule_cleanup* + * ib_drain_qp() also won't be called. + */ + if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { + sc->status = SMBDIRECT_SOCKET_DISCONNECTED; + smbdirect_socket_wake_up_all(sc); + } + if (sc->ib.qp) { smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, "drain qp\n"); @@ -679,17 +711,21 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc) "destroying rdma session\n"); if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING) smbdirect_socket_cleanup_work(&sc->disconnect_work); - if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "wait for transport being disconnected\n"); - wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED); - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "waited for transport being disconnected\n"); - } /* - * Once we reached SMBDIRECT_SOCKET_DISCONNECTED, - * we should call smbdirect_socket_destroy() + * We don't wait for RDMA_CM_EVENT_DISCONNECTED, + * rdma_disconnect() was already called by + * smbdirect_socket_cleanup_work() if needed + * and smbdirect_socket_destroy() drains the qp, + * destroys it and calls rdma_destroy_id(), which + * only waits for a currently running event handler + * and makes sure no further events are delivered. + * The rest of the disconnect protocol is handled + * by the rdma core asynchronously. + * + * Waiting for RDMA_CM_EVENT_DISCONNECTED could + * take very long or forever, e.g. if the peer + * just disappeared. */ smbdirect_socket_destroy(sc); smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, -- 2.43.0 From metze at samba.org Mon Oct 5 18:35:51 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:35:51 +0200 Subject: [PATCH 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: A child of a listener only left the listen.pending or listen.ready list via smbdirect_socket_accept() or when the listener was destroyed. So every connection that failed before it was accepted (negotiate timeout, peer disconnect, invalid negotiate request, ...) stayed on the list and counted against the backlog until the listener was destroyed. After about backlog (10 for ksmbd) such connections smbdirect_listen_connect_request() rejected every new connection with -EBUSY, so an unauthenticated peer was able to permanently stop ksmbd from accepting SMB-Direct connections. Now a failed child moves itself to a new listen.orphaned list at the end of smbdirect_socket_cleanup_work(), after the disconnect was started, and queues listen.purge_orphaned_work, which releases it with smbdirect_socket_release(), without holding the listener's rdma_lock_handler() lock, in the same way smbdirect_socket_destroy() does for the remaining children. Orphaned sockets still count against the backlog until they are released. sc->accept.listener is now only changed under listener->listen.lock and whoever clears it is responsible for releasing the socket: smbdirect_socket_accept(), purge_orphaned_work or the destroy of the listener. In order to make that work: - a struct smbdirect_socket that is a listener is now freed via kfree_rcu(), so that a child can dereference sc->accept.listener under rcu_read_lock(), even if the listener is released concurrently. - smbdirect_listen_connect_request() only sets nsc->accept.listener after smbdirect_accept_connect_request() succeeded, so that a failure within it can't let nsc orphan itself before we release it. - smbdirect_accept_negotiate_recv_work() no longer moves a socket to the ready list if it already failed or no longer belongs to the listener. Every accepting socket has a listener, so if sc->accept.listener is already cleared, it no longer sends a negotiate response, as the socket is about to be released. - smbdirect_socket_destroy() of the listener disables purge_orphaned_work and also releases the orphaned sockets. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: ???????? Closes: https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 67 ++++++++++++++------- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 116 ++++++++++++++++++++++++++++++++++-- fs/smb/smbdirect/socket.c | 57 +++++++++++++++++- fs/smb/smbdirect/socket.h | 37 ++++++++++++ 5 files changed, 252 insertions(+), 27 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1b30ca8476c1..1da95cdd1ebd 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -302,6 +302,7 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, connect.work); struct smbdirect_socket_parameters *sp = &sc->parameters; + struct smbdirect_socket *lsc; struct smbdirect_recv_io *recv_io; struct smbdirect_negotiate_req *nreq; unsigned long flags; @@ -464,29 +465,55 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) */ sp->max_fragmented_send_size = max_fragmented_size; - if (sc->accept.listener) { - struct smbdirect_socket *lsc = sc->accept.listener; - unsigned long flags; + /* + * Every accepting socket was created by + * smbdirect_listen_connect_request() and has a + * listener. If it's already cleared, the listener + * (or its purge_orphaned_work) is responsible for + * releasing us, so we must not send a negotiate + * response. + * + * The memory of the listener is freed via kfree_rcu(), + * see smbdirect_listen_orphan_socket(). + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del(&sc->accept.list); - list_add_tail(&sc->accept.list, &lsc->listen.ready); + spin_lock_irqsave(&lsc->listen.lock, flags); + /* + * The listener (or its purge_orphaned_work) + * may have cleared sc->accept.listener in the + * meantime and is responsible for releasing us. + * + * If we already failed, we're either + * already on the orphaned list or + * smbdirect_socket_cleanup_work() will + * move us there. + * + * In both cases we must not move us + * to the ready list. + */ + if (sc->accept.listener == lsc && !READ_ONCE(sc->first_error)) { + list_move_tail(&sc->accept.list, &lsc->listen.ready); wake_up(&lsc->listen.wait_queue); - spin_unlock_irqrestore(&lsc->listen.lock, flags); - - /* - * smbdirect_socket_accept() will call - * smbdirect_accept_negotiate_finish(nsc, 0); - * - * So that we don't send the negotiation - * response that grants credits to the peer - * before the socket is accepted by the - * application. - */ - return; } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); - ntstatus = le32_to_cpu(STATUS_SUCCESS); + /* + * smbdirect_socket_accept() will call + * smbdirect_accept_negotiate_finish(nsc, 0); + * + * So that we don't send the negotiation + * response that grants credits to the peer + * before the socket is accepted by the + * application. + */ + return; not_supported: smbdirect_accept_negotiate_finish(sc, ntstatus); @@ -839,7 +866,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct smbdirect_socket, accept.list); if (nsc) { - nsc->accept.listener = NULL; + WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); arg->is_empty = list_empty_careful(&lsc->listen.ready); } diff --git a/fs/smb/smbdirect/internal.h b/fs/smb/smbdirect/internal.h index e9959e6dc13a..69d0c9f899de 100644 --- a/fs/smb/smbdirect/internal.h +++ b/fs/smb/smbdirect/internal.h @@ -74,6 +74,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc); +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc); + int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc, enum smbdirect_socket_status expected_status, int unexpected_errno, diff --git a/fs/smb/smbdirect/listen.c b/fs/smb/smbdirect/listen.c index 2f78bcaedbf8..b30c0aa37e3a 100644 --- a/fs/smb/smbdirect/listen.c +++ b/fs/smb/smbdirect/listen.c @@ -10,6 +10,78 @@ static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event); +/* + * This is called by a socket that failed while it + * is still on the pending or ready list of its + * listener, typically at the end of + * smbdirect_socket_cleanup_work(), when the + * disconnect was already started. + * + * It moves itself to the orphaned list and + * lets smbdirect_listen_purge_orphaned_work() + * release it. Otherwise it would stay on the + * pending or ready list until the listener is + * destroyed and fill up the backlog, so that + * no new connections would be accepted. + * + * It's fine to call this more than once. + */ +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc) +{ + struct smbdirect_socket *lsc; + unsigned long flags; + + /* + * The memory of the listener is freed via + * kfree_rcu(), so it's safe to dereference it + * under rcu_read_lock(), even if sc->accept.listener + * is cleared and the listener is released concurrently. + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } + + spin_lock_irqsave(&lsc->listen.lock, flags); + if (sc->accept.listener == lsc) { + list_move_tail(&sc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); +} + +static void smbdirect_listen_purge_orphaned_work(struct work_struct *work) +{ + struct smbdirect_socket *lsc = + container_of(work, struct smbdirect_socket, listen.purge_orphaned_work); + struct smbdirect_socket *psc, *tsc; + LIST_HEAD(orphaned_list); + unsigned long flags; + + /* + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them. + */ + spin_lock_irqsave(&lsc->listen.lock, flags); + list_splice_tail_init(&lsc->listen.orphaned, &orphaned_list); + list_for_each_entry(psc, &orphaned_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); + spin_unlock_irqrestore(&lsc->listen.lock, flags); + + /* + * We don't hold the listener's rdma_lock_handler() + * lock here, see smbdirect_socket_destroy() + * for why that's important. + */ + list_for_each_entry_safe(psc, tsc, &orphaned_list, accept.list) { + list_del_init(&psc->accept.list); + smbdirect_socket_release(psc); + } +} + int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) { int ret; @@ -47,6 +119,9 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler; rdma_unlock_handler(sc->rdma.cm_id); + INIT_WORK(&sc->listen.purge_orphaned_work, + smbdirect_listen_purge_orphaned_work); + ret = rdma_listen(sc->rdma.cm_id, backlog); if (ret) { sc->first_error = ret; @@ -214,6 +289,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, size_t backlog = max_t(size_t, 1, lsc->listen.backlog); size_t psockets; size_t rsockets; + size_t osockets; int ret; if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) { @@ -254,14 +330,21 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, spin_lock_irqsave(&lsc->listen.lock, flags); psockets = list_count_nodes(&lsc->listen.pending); rsockets = list_count_nodes(&lsc->listen.ready); + /* + * Orphaned sockets still count against + * the backlog until they are released by + * smbdirect_listen_purge_orphaned_work(). + */ + osockets = list_count_nodes(&lsc->listen.orphaned); spin_unlock_irqrestore(&lsc->listen.lock, flags); if (psockets > backlog || rsockets > backlog || - (psockets + rsockets) > backlog) { + osockets > backlog || + (psockets + rsockets + osockets) > backlog) { smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR, - "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n", - lsc->listen.backlog, backlog, psockets, rsockets); + "Backlog[%d][%zu] full pending[%zu] ready[%zu] orphaned[%zu]\n", + lsc->listen.backlog, backlog, psockets, rsockets, osockets); return -EBUSY; } @@ -279,21 +362,44 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, if (ret) goto set_settings_failed; + /* + * Note that nsc->accept.listener is only set + * after smbdirect_accept_connect_request() + * succeeded, so that a failure within it + * can't let nsc orphan itself via + * smbdirect_listen_orphan_socket(), as we + * release it below in that case. + * + * The listener's handler_mutex is held while + * we're called, so smbdirect_socket_destroy() + * of the listener can't see nsc on the + * pending list before we're done. + */ spin_lock_irqsave(&lsc->listen.lock, flags); list_add_tail(&nsc->accept.list, &lsc->listen.pending); - nsc->accept.listener = lsc; spin_unlock_irqrestore(&lsc->listen.lock, flags); ret = smbdirect_accept_connect_request(nsc, &event->param.conn); if (ret) goto accept_connect_failed; + spin_lock_irqsave(&lsc->listen.lock, flags); + WRITE_ONCE(nsc->accept.listener, lsc); + spin_unlock_irqrestore(&lsc->listen.lock, flags); + + /* + * If nsc already failed, its + * smbdirect_socket_cleanup_work() may + * have missed the listener. + */ + if (READ_ONCE(nsc->first_error)) + smbdirect_listen_orphan_socket(nsc); + return 0; accept_connect_failed: spin_lock_irqsave(&lsc->listen.lock, flags); list_del_init(&nsc->accept.list); - nsc->accept.listener = NULL; spin_unlock_irqrestore(&lsc->listen.lock, flags); set_settings_failed: set_params_failed: diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index c36cb7cc0088..e270e36b3145 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -319,6 +319,15 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, * (smbdirect_socket_destroy) to reap. */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -427,6 +436,15 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * instances of one class -- harmless, but lockdep cannot tell). */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -486,6 +504,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * in order to notice the broken connection. */ smbdirect_socket_wake_up_all(sc); + + /* + * If we're still on the pending or ready list + * of a listener, we started the disconnect + * as far as possible above, so we move ourself + * to the orphaned list of the listener, + * which will release us. + * + * This is a no-op internally if + * sc->accept.listener is NULL. + */ + smbdirect_listen_orphan_socket(sc); } static void smbdirect_socket_destroy(struct smbdirect_socket *sc) @@ -544,6 +574,7 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) disable_work_sync(&sc->recv_io.posted.refill_work); disable_work_sync(&sc->idle.immediate_work); disable_delayed_work_sync(&sc->idle.timer_work); + disable_work_sync(&sc->listen.purge_orphaned_work); if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); @@ -607,6 +638,19 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) spin_lock_irqsave(&sc->listen.lock, flags); list_splice_tail_init(&sc->listen.ready, &pending_list); list_splice_tail_init(&sc->listen.pending, &pending_list); + /* + * purge_orphaned_work is already disabled above, + * so we also need to release the orphaned sockets. + * + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them and + * prevents them from moving themselves to + * the orphaned list via + * smbdirect_listen_orphan_socket(). + */ + list_splice_tail_init(&sc->listen.orphaned, &pending_list); + list_for_each_entry(psc, &pending_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); spin_unlock_irqrestore(&sc->listen.lock, flags); /* It's not possible for upper layer to get to reassembly */ @@ -650,7 +694,6 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) "release %zu pending sockets\n", psockets); list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) { list_del_init(&psc->accept.list); - psc->accept.listener = NULL; smbdirect_socket_release(psc); } if (sc->listen.backlog != -1) /* was a listener */ @@ -777,7 +820,17 @@ static void smbdirect_socket_release_destroy(struct kref *kref) * in DESTROYED state, before we free the memory. */ smbdirect_socket_destroy_sync(sc); - kfree(sc); + + /* + * Only a listener (backlog != -1) is ever dereferenced + * via sc->accept.listener under rcu_read_lock(), see + * smbdirect_listen_orphan_socket(). Other sockets can be + * freed immediately. + */ + if (sc->listen.backlog != -1) /* was a listener */ + kfree_rcu(sc, refs.rcu); + else + kfree(sc); } void smbdirect_socket_release(struct smbdirect_socket *sc) diff --git a/fs/smb/smbdirect/socket.h b/fs/smb/smbdirect/socket.h index c09eddd8ad16..36623980ec17 100644 --- a/fs/smb/smbdirect/socket.h +++ b/fs/smb/smbdirect/socket.h @@ -151,6 +151,12 @@ struct smbdirect_socket { * the disconnect refcount. */ struct kref destroy; + + /* + * smbdirect_socket_release_destroy() uses + * kfree_rcu(), see accept.listener. + */ + struct rcu_head rcu; } refs; /* RDMA related */ @@ -216,6 +222,16 @@ struct smbdirect_socket { * only be > 0. */ int backlog; + /* + * Sockets on pending or ready that failed + * move themselves to orphaned and queue + * purge_orphaned_work, which releases them. + * So that they are freed while the listener + * is still alive. They still count against + * the backlog until they are released. + */ + struct list_head orphaned; + struct work_struct purge_orphaned_work; } listen; /* @@ -226,7 +242,25 @@ struct smbdirect_socket { * connection. */ struct { + /* + * This is only set, protected by + * listener->listen.lock, while the socket + * is owned by the listener (on its pending, + * ready or orphaned list). The one who clears + * it is responsible for releasing the socket. + * + * The memory of a struct smbdirect_socket is + * freed via kfree_rcu(), so the listener + * can be dereferenced under rcu_read_lock(), + * even if accept.listener is cleared and + * the listener is released concurrently. + */ struct smbdirect_socket *listener; + /* + * Note even with listener being NULL, + * the socket can be on listeners.listen.pending + * before smbdirect_accept_connect_request() finished. + */ struct list_head list; } accept; @@ -588,6 +622,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) spin_lock_init(&sc->listen.lock); INIT_LIST_HEAD(&sc->listen.pending); INIT_LIST_HEAD(&sc->listen.ready); + INIT_LIST_HEAD(&sc->listen.orphaned); + INIT_WORK(&sc->listen.purge_orphaned_work, __smbdirect_socket_disabled_work); + disable_work_sync(&sc->listen.purge_orphaned_work); sc->listen.backlog = -1; /* not a listener */ init_waitqueue_head(&sc->listen.wait_queue); -- 2.43.0 From metze at samba.org Mon Oct 5 18:35:52 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:35:52 +0200 Subject: [PATCH 3/3] smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() In-Reply-To: References: Message-ID: <3f341ddacb503cc581f853c7e283f40fab6f1495.1791224972.git.metze@samba.org> A socket on the ready list is in SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, but it may fail before smbdirect_socket_accept() picks it up, e.g. RDMA_CM_EVENT_DISCONNECTED already moved it to SMBDIRECT_SOCKET_DISCONNECTED. smbdirect_socket_accept() unconditionally overwrote the status with SMBDIRECT_SOCKET_CONNECTED and handed out the already disconnected socket. smbdirect_socket_cleanup_work() then called rdma_disconnect() on it again, and before the recent commit smbdirect_socket_destroy_sync() waited forever for an RDMA_CM_EVENT_DISCONNECTED that already happened. Now we check first_error and change the status to SMBDIRECT_SOCKET_CONNECTED only via cmpxchg() from SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, while we still hold listen.lock. If the socket already failed, we move it to the orphaned list and queue purge_orphaned_work, like smbdirect_listen_orphan_socket() does, and try the next one. If we only found failed sockets, we wait for the next one. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 55 ++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1da95cdd1ebd..ee16be95424d 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -834,8 +834,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct proto_accept_arg *arg) { struct smbdirect_socket *nsc; + bool orphaned; unsigned long flags; +again: + orphaned = false; + if (lsc->status != SMBDIRECT_SOCKET_LISTENING) { arg->err = -EINVAL; return NULL; @@ -862,9 +866,38 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } spin_lock_irqsave(&lsc->listen.lock, flags); - nsc = list_first_entry_or_null(&lsc->listen.ready, - struct smbdirect_socket, - accept.list); + while ((nsc = list_first_entry_or_null(&lsc->listen.ready, + struct smbdirect_socket, + accept.list))) { + /* + * nsc may have failed after it was moved + * to the ready list, e.g. RDMA_CM_EVENT_DISCONNECTED + * already moved it to SMBDIRECT_SOCKET_DISCONNECTED. + * We must not overwrite that with + * SMBDIRECT_SOCKET_CONNECTED and hand + * out an already disconnected socket. + * + * Doing this under listen.lock means nsc + * still belongs to us, so we can just move + * a failed socket to the orphaned list, + * like smbdirect_listen_orphan_socket() does, + * and try the next one. + */ + if (!READ_ONCE(nsc->first_error) && + cmpxchg(&nsc->status, + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, + SMBDIRECT_SOCKET_CONNECTED) == + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) + break; + + smbdirect_log_rdma_event(nsc, SMBDIRECT_LOG_INFO, + "orphaning failed socket status=%s first_error=%1pe\n", + smbdirect_socket_status_string(nsc->status), + SMBDIRECT_DEBUG_ERR_PTR(nsc->first_error)); + list_move_tail(&nsc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + orphaned = true; + } if (nsc) { WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); @@ -872,6 +905,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } spin_unlock_irqrestore(&lsc->listen.lock, flags); if (!nsc) { + /* + * If we only found failed sockets, + * we wait for the next one. + */ + if (orphaned) + goto again; arg->err = -EAGAIN; return NULL; } @@ -882,12 +921,14 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, * so it didn't grant any credits to us. * * The caller expects a connected socket - * now as there are no credits anyway. + * now as there are no credits anyway, + * above we already changed to SMBDIRECT_SOCKET_CONNECTED + * under the lsc->listen.lock and with cmpxchg. * - * Then we send the negotiation response in - * order to grant credits to the peer. + * Now we send the negotiation response in + * order to grant credits to the peer, + * as the socket is now visible to the application layer. */ - nsc->status = SMBDIRECT_SOCKET_CONNECTED; smbdirect_accept_negotiate_finish(nsc, 0); return nsc; -- 2.43.0 From metze at samba.org Mon Oct 5 18:46:55 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:46:55 +0200 Subject: [PATCH 0/5] smb: client: don't use server->smbd_conn without a reference or lock Message-ID: Hi Paulo/Namjae, The SMB Direct client keeps the transport in server->smbd_conn and accesses it from several places without any locking: - smbd_get_parameters() - smbd_register_mr() - smbd_debug_proc_show() At the same time cifs_abort_connection() (and the reconnect path) can call smbd_destroy() at any time, which frees the smbd_connection and releases the underlying smbdirect socket. So the readers above can race with the teardown and end up using a freed connection/socket, a use-after-free. While looking at this, smbd_get_parameters() also turned out to dereference server->smbd_conn without checking it, which is a NULL pointer dereference in smb2_negotiate_wsize()/smb2_negotiate_rsize() when it is called during a reconnect window. This series closes the races: - server->smbd_conn is only ever changed under server->srv_lock, so readers can safely look at it while holding that lock. - smbdirect gets smbdirect_socket_get()/smbdirect_socket_put(), which take and drop a reference that keeps the socket memory alive (but does not keep it connected). smbd_register_mr() uses them to hold the socket across smbdirect_connection_register_mr_io(); once the connection is gone the socket is no longer connected and the call fails gracefully. A registered MR has its own reference counting. - smbd_get_parameters() no longer returns a pointer into the socket (which cannot be kept valid for the callers). It copies the current parameters into the new server->smbd_params under server->srv_lock and returns a pointer to that; with no connection it returns zeros. - smbd_debug_proc_show() runs with cifs_tcp_ses_lock held, so it takes server->srv_lock instead of a socket reference. The two "pass server to ..." patches are mechanical prototype changes with no behaviour change, split out so the final fix stays small and each step builds on its own. All of this is a use-after-free that goes back to the switch to the smbdirect socket API, so the whole series is tagged for stable via Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") which is in mainline since v7.1-rc1. I run various xfstests with this. I'm unsure if this should go via cifs-next or ksmbd-for-next, for consistency with the series I just send I guess both could go via ksmbd-for-next? Stefan Metzmacher (5): smb: client: change server->smbd_conn only under server->srv_lock smb: smbdirect: add smbdirect_socket_get() and smbdirect_socket_put() smb: client: pass server to smbd_get_parameters() smb: client: pass server to smbd_register_mr() smb: client: don't use server->smbd_conn without a reference or lock fs/smb/client/cifsglob.h | 12 ++++ fs/smb/client/connect.c | 12 +++- fs/smb/client/file.c | 4 +- fs/smb/client/smb2ops.c | 4 +- fs/smb/client/smb2pdu.c | 4 +- fs/smb/client/smbdirect.c | 124 +++++++++++++++++++++++++++++++++----- fs/smb/client/smbdirect.h | 8 ++- fs/smb/smbdirect/socket.c | 27 +++++++++ include/linux/smbdirect.h | 3 + 9 files changed, 171 insertions(+), 27 deletions(-) -- 2.43.0 From metze at samba.org Mon Oct 5 18:46:56 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:46:56 +0200 Subject: [PATCH 1/5] smb: client: change server->smbd_conn only under server->srv_lock In-Reply-To: References: Message-ID: smbd_get_parameters(), smbd_register_mr() and smbd_debug_proc_show() use server->smbd_conn without holding cifs_server_lock(), while cifs_abort_connection() can destroy it via smbd_destroy() at any time, which frees the smbd_connection and releases the smbdirect socket. smbd_debug_proc_show() is called with cifs_tcp_ses_lock held, so cifs_server_lock() can't be used there. Change server->smbd_conn only under server->srv_lock, via the new smbd_set_connection() and in smbd_destroy(), which now clears the pointer under the lock before releasing the socket. This allows readers to use server->smbd_conn under server->srv_lock. cifs_get_tcp_session() calls smbd_destroy() on failure, also for early failures before server->srv_lock is initialized, so only call it if server->smbd_conn is set. Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") Cc: Paulo Alcantara Cc: Namjae Jeon Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/client/connect.c | 12 +++++++++--- fs/smb/client/smbdirect.c | 29 +++++++++++++++++++++++++---- fs/smb/client/smbdirect.h | 4 ++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 28e1ddeb6182..a45411037253 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1858,8 +1858,9 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, rc = -ENOENT; goto out_err_crypto_release; #endif - tcp_ses->smbd_conn = smbd_get_connection( - tcp_ses, (struct sockaddr *)&ctx->dstaddr); + smbd_set_connection(tcp_ses, + smbd_get_connection(tcp_ses, + (struct sockaddr *)&ctx->dstaddr)); if (tcp_ses->smbd_conn) { cifs_dbg(VFS, "RDMA transport established\n"); rc = 0; @@ -1934,7 +1935,12 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, kfree(tcp_ses->leaf_fullpath); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); - smbd_destroy(tcp_ses); + /* + * srv_lock is not initialized yet for + * the early gotos, but smbd_conn is NULL then. + */ + if (tcp_ses->smbd_conn) + smbd_destroy(tcp_ses); kfree(tcp_ses); } return ERR_PTR(rc); diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 563ef488a225..3ecf34c7379a 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -201,7 +201,17 @@ static int smbd_post_send_full_iter(struct smbdirect_socket *sc, */ void smbd_destroy(struct TCP_Server_Info *server) { - struct smbd_connection *info = server->smbd_conn; + struct smbd_connection *info; + + /* + * server->smbd_conn is changed under server->srv_lock, + * so that readers which don't hold cifs_server_lock() + * can use it under server->srv_lock. + */ + spin_lock(&server->srv_lock); + info = server->smbd_conn; + server->smbd_conn = NULL; + spin_unlock(&server->srv_lock); if (!info) { log_rdma_event(INFO, "rdma session already destroyed\n"); @@ -211,7 +221,17 @@ void smbd_destroy(struct TCP_Server_Info *server) smbdirect_socket_release(info->socket); kfree(info); - server->smbd_conn = NULL; +} + +/* + * Publish a new connection, see smbd_destroy() + */ +void smbd_set_connection(struct TCP_Server_Info *server, + struct smbd_connection *info) +{ + spin_lock(&server->srv_lock); + server->smbd_conn = info; + spin_unlock(&server->srv_lock); } /* @@ -236,8 +256,9 @@ int smbd_reconnect(struct TCP_Server_Info *server) create_conn: log_rdma_event(INFO, "creating rdma session\n"); - server->smbd_conn = smbd_get_connection( - server, (struct sockaddr *) &server->dstaddr); + smbd_set_connection(server, + smbd_get_connection(server, + (struct sockaddr *) &server->dstaddr)); if (server->smbd_conn) { cifs_dbg(VFS, "RDMA transport re-established\n"); diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h index be205ec02077..54ee49f9cfc6 100644 --- a/fs/smb/client/smbdirect.h +++ b/fs/smb/client/smbdirect.h @@ -30,6 +30,8 @@ struct smbd_connection { /* Create a SMBDirect session */ struct smbd_connection *smbd_get_connection( struct TCP_Server_Info *server, struct sockaddr *dstaddr); +void smbd_set_connection(struct TCP_Server_Info *server, + struct smbd_connection *info); const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn); @@ -58,6 +60,8 @@ void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m); struct smbd_connection {}; static inline void *smbd_get_connection( struct TCP_Server_Info *server, struct sockaddr *dstaddr) {return NULL;} +static inline void smbd_set_connection(struct TCP_Server_Info *server, + struct smbd_connection *info) {} static inline int smbd_reconnect(struct TCP_Server_Info *server) {return -1; } static inline void smbd_destroy(struct TCP_Server_Info *server) {} static inline int smbd_recv(struct smbd_connection *info, struct msghdr *msg) {return -1; } -- 2.43.0 From metze at samba.org Mon Oct 5 18:46:57 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:46:57 +0200 Subject: [PATCH 2/5] smb: smbdirect: add smbdirect_socket_get() and smbdirect_socket_put() In-Reply-To: References: Message-ID: <424edd9cd03a71efcc95cc2f8441145694f9f30f.1791225453.git.metze@samba.org> These take and drop a reference on sc->refs.destroy, which only keeps the memory of the socket alive. smbdirect_socket_release() still disconnects and destroys the socket, after that it is no longer connected and all functions fail gracefully. This will be used by the smb client in order to use the socket, e.g. for smbdirect_connection_register_mr_io(), while cifs_abort_connection() may release it at the same time. Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") Cc: Paulo Alcantara Cc: Namjae Jeon Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/socket.c | 27 +++++++++++++++++++++++++++ include/linux/smbdirect.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index e270e36b3145..13cf50b96bc9 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -851,6 +851,33 @@ void smbdirect_socket_release(struct smbdirect_socket *sc) } EXPORT_SYMBOL_GPL(smbdirect_socket_release); +/* + * smbdirect_socket_get() and smbdirect_socket_put() only keep + * the memory of the socket alive, they don't prevent + * smbdirect_socket_release() from disconnecting and destroying + * it, after that the socket is no longer connected and + * all functions fail gracefully. + * + * This allows a caller to use the socket, e.g. for + * smbdirect_connection_register_mr_io(), while another + * thread may call smbdirect_socket_release() at the same time. + * + * smbdirect_socket_put() may free the socket, + * so it must be called from process context. + */ +void smbdirect_socket_get(struct smbdirect_socket *sc) +{ + kref_get(&sc->refs.destroy); +} +EXPORT_SYMBOL_GPL(smbdirect_socket_get); + +void smbdirect_socket_put(struct smbdirect_socket *sc) +{ + might_sleep(); + kref_put(&sc->refs.destroy, smbdirect_socket_release_destroy); +} +EXPORT_SYMBOL_GPL(smbdirect_socket_put); + int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc, enum smbdirect_socket_status expected_status, int unexpected_errno, diff --git a/include/linux/smbdirect.h b/include/linux/smbdirect.h index 97f5ba730fa7..7f0b1ed6cf16 100644 --- a/include/linux/smbdirect.h +++ b/include/linux/smbdirect.h @@ -111,6 +111,9 @@ void smbdirect_socket_shutdown(struct smbdirect_socket *sc); void smbdirect_socket_release(struct smbdirect_socket *sc); +void smbdirect_socket_get(struct smbdirect_socket *sc); +void smbdirect_socket_put(struct smbdirect_socket *sc); + int smbdirect_connection_send_batch_flush(struct smbdirect_socket *sc, struct smbdirect_send_batch *batch, bool is_last); -- 2.43.0 From metze at samba.org Mon Oct 5 18:46:58 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:46:58 +0200 Subject: [PATCH 3/5] smb: client: pass server to smbd_get_parameters() In-Reply-To: References: Message-ID: This is a mechanical change of the prototype of smbd_get_parameters() to take a struct TCP_Server_Info *server instead of a struct smbd_connection *, deriving server->smbd_conn inside the function, with no change in behaviour. A following change uses this to access server->smbd_conn under server->srv_lock. smbd_get_connection() is called before the connection is published in server->smbd_conn, so it uses smbdirect_socket_get_current_parameters() directly. Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") Cc: Paulo Alcantara Cc: Namjae Jeon Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/client/file.c | 4 ++-- fs/smb/client/smb2ops.c | 4 ++-- fs/smb/client/smbdirect.c | 8 +++++--- fs/smb/client/smbdirect.h | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 0d428517f454..632d1f24e36b 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -98,7 +98,7 @@ static void cifs_prepare_write(struct netfs_io_subrequest *subreq) #ifdef CONFIG_CIFS_SMB_DIRECT if (server->smbd_conn) { const struct smbdirect_socket_parameters *sp = - smbd_get_parameters(server->smbd_conn); + smbd_get_parameters(server); stream->sreq_max_segs = sp->max_frmr_depth; } @@ -192,7 +192,7 @@ static int cifs_prepare_read(struct netfs_io_subrequest *subreq) #ifdef CONFIG_CIFS_SMB_DIRECT if (server->smbd_conn) { const struct smbdirect_socket_parameters *sp = - smbd_get_parameters(server->smbd_conn); + smbd_get_parameters(server); rreq->io_streams[0].sreq_max_segs = sp->max_frmr_depth; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3464470d3297..1b2ef128810f 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -516,7 +516,7 @@ smb3_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx) #ifdef CONFIG_CIFS_SMB_DIRECT if (server->rdma) { const struct smbdirect_socket_parameters *sp = - smbd_get_parameters(server->smbd_conn); + smbd_get_parameters(server); if (server->sign) /* @@ -567,7 +567,7 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx) #ifdef CONFIG_CIFS_SMB_DIRECT if (server->rdma) { const struct smbdirect_socket_parameters *sp = - smbd_get_parameters(server->smbd_conn); + smbd_get_parameters(server); if (server->sign) /* diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 3ecf34c7379a..41856261c372 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -361,8 +361,10 @@ static struct smbd_connection *_smbd_get_connection( return NULL; } -const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn) +const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server) { + struct smbd_connection *conn = server->smbd_conn; + if (unlikely(!conn->socket)) { static const struct smbdirect_socket_parameters zero_params; @@ -390,7 +392,7 @@ struct smbd_connection *smbd_get_connection( if (!ret) return NULL; - sp = smbd_get_parameters(ret); + sp = smbdirect_socket_get_current_parameters(ret->socket); server->rdma_readwrite_threshold = rdma_readwrite_threshold > sp->max_fragmented_send_size ? @@ -435,7 +437,7 @@ int smbd_send(struct TCP_Server_Info *server, { struct smbd_connection *info = server->smbd_conn; struct smbdirect_socket *sc = info->socket; - const struct smbdirect_socket_parameters *sp = smbd_get_parameters(info); + const struct smbdirect_socket_parameters *sp = smbd_get_parameters(server); struct smb_rqst *rqst; struct iov_iter iter; struct smbdirect_send_batch_storage bstorage; diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h index 54ee49f9cfc6..84fab8b6d1f0 100644 --- a/fs/smb/client/smbdirect.h +++ b/fs/smb/client/smbdirect.h @@ -33,7 +33,7 @@ struct smbd_connection *smbd_get_connection( void smbd_set_connection(struct TCP_Server_Info *server, struct smbd_connection *info); -const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn); +const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server); /* Reconnect SMBDirect session */ int smbd_reconnect(struct TCP_Server_Info *server); -- 2.43.0 From metze at samba.org Mon Oct 5 18:46:59 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:46:59 +0200 Subject: [PATCH 4/5] smb: client: pass server to smbd_register_mr() In-Reply-To: References: Message-ID: This is a mechanical change of the prototype of smbd_register_mr() to take a struct TCP_Server_Info *server instead of a struct smbd_connection *, deriving server->smbd_conn inside the function, with no change in behaviour. A following change uses this to take a reference on server->smbd_conn's socket under server->srv_lock. Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") Cc: Paulo Alcantara Cc: Namjae Jeon Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/client/smb2pdu.c | 4 ++-- fs/smb/client/smbdirect.c | 3 ++- fs/smb/client/smbdirect.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 880ce12f50c4..aa5a33ca5d08 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4598,7 +4598,7 @@ smb2_new_read_req(void **buf, unsigned int *total_len, struct smbdirect_buffer_descriptor_v1 *v1; bool need_invalidate = server->dialect == SMB30_PROT_ID; - rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter, + rdata->mr = smbd_register_mr(server, &rdata->subreq.io_iter, true, need_invalidate); if (!rdata->mr) { rc = -EAGAIN; @@ -5200,7 +5200,7 @@ smb2_async_writev(struct cifs_io_subrequest *wdata) struct smbdirect_buffer_descriptor_v1 *v1; bool need_invalidate = server->dialect == SMB30_PROT_ID; - wdata->mr = smbd_register_mr(server->smbd_conn, &wdata->subreq.io_iter, + wdata->mr = smbd_register_mr(server, &wdata->subreq.io_iter, false, need_invalidate); if (!wdata->mr) { rc = -EAGAIN; diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 41856261c372..942763789335 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -537,10 +537,11 @@ int smbd_send(struct TCP_Server_Info *server, * need_invalidate: true if this MR needs to be locally invalidated after I/O * return value: the MR registered, NULL if failed. */ -struct smbdirect_mr_io *smbd_register_mr(struct smbd_connection *info, +struct smbdirect_mr_io *smbd_register_mr(struct TCP_Server_Info *server, struct iov_iter *iter, bool writing, bool need_invalidate) { + struct smbd_connection *info = server->smbd_conn; struct smbdirect_socket *sc = info->socket; if (!smbdirect_connection_is_connected(sc)) diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h index 84fab8b6d1f0..1e1cf9a0104c 100644 --- a/fs/smb/client/smbdirect.h +++ b/fs/smb/client/smbdirect.h @@ -47,7 +47,7 @@ int smbd_send(struct TCP_Server_Info *server, /* Interfaces to register and deregister MR for RDMA read/write */ struct smbdirect_mr_io *smbd_register_mr( - struct smbd_connection *info, struct iov_iter *iter, + struct TCP_Server_Info *server, struct iov_iter *iter, bool writing, bool need_invalidate); void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr, struct smbdirect_buffer_descriptor_v1 *v1); -- 2.43.0 From metze at samba.org Mon Oct 5 18:47:00 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 20:47:00 +0200 Subject: [PATCH 5/5] smb: client: don't use server->smbd_conn without a reference or lock In-Reply-To: References: Message-ID: smbd_get_parameters(), smbd_register_mr() and smbd_debug_proc_show() used server->smbd_conn without any locking, while cifs_abort_connection() can destroy it via smbd_destroy() at any time, which frees the smbd_connection and releases the smbdirect socket. smbd_register_mr() now uses the new smbd_get_socket() and smbd_put_socket(), which take and drop a reference on the socket of server->smbd_conn under server->srv_lock via smbdirect_socket_get() and smbdirect_socket_put(). Once the connection is destroyed, the socket is no longer connected, so smbdirect_connection_register_mr_io() fails gracefully. A registered mr has its own reference counting. smbd_debug_proc_show() is called with cifs_tcp_ses_lock held, so it holds server->srv_lock instead, which prevents server->smbd_conn from being destroyed. smbd_get_parameters() returned a pointer into the socket, which can't be kept valid for the callers. Now it copies the parameters into the new server->smbd_params under server->srv_lock and returns a pointer to that. If there's no connection (anymore), server->smbd_params is set to zeros. This also fixes a possible NULL pointer dereference in smb2_negotiate_wsize() and smb2_negotiate_rsize(), which called smbd_get_parameters(server->smbd_conn) without checking server->smbd_conn, e.g. during a reconnect. smbd_send() is called under cifs_server_lock(), so it uses smbdirect_socket_get_current_parameters() directly. Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") Cc: Paulo Alcantara Cc: Namjae Jeon Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/client/cifsglob.h | 12 +++++ fs/smb/client/smbdirect.c | 94 +++++++++++++++++++++++++++++++++------ 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 79e4e84f8985..c3efbc3d1f2a 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -30,6 +30,9 @@ #include "smb2pdu.h" #include "smb1pdu.h" #include +#ifdef CONFIG_CIFS_SMB_DIRECT +#include +#endif #define SMB_PATH_MAX 260 #define CIFS_PORT 445 @@ -761,6 +764,15 @@ struct TCP_Server_Info { bool rdma; /* point to the SMBD connection if RDMA is used instead of socket */ struct smbd_connection *smbd_conn; +#ifdef CONFIG_CIFS_SMB_DIRECT + /* + * A copy of the current smbdirect socket parameters, + * updated by smbd_get_parameters(). smbd_conn and + * its socket can be destroyed at any time, so callers + * can't get a pointer into it. + */ + struct smbdirect_socket_parameters smbd_params; +#endif struct delayed_work echo; /* echo ping workqueue job */ char *smallbuf; /* pointer to current "small" buffer */ char *bigbuf; /* pointer to current "big" buffer */ diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 942763789335..e59bf9a48ea0 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -361,17 +361,61 @@ static struct smbd_connection *_smbd_get_connection( return NULL; } -const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server) +/* + * server->smbd_conn can be destroyed by cifs_abort_connection() + * at any time, so we take a reference on its socket while we use + * it. server->smbd_conn is changed under server->srv_lock, + * see smbd_set_connection() and smbd_destroy(). + * + * Once the connection is destroyed, the socket is no longer + * connected, so the smbdirect functions fail gracefully. + * + * smbd_put_socket() may free the socket, so this + * can only be used in process context. + */ +static struct smbdirect_socket *smbd_get_socket(struct TCP_Server_Info *server) { - struct smbd_connection *conn = server->smbd_conn; + struct smbdirect_socket *sc = NULL; - if (unlikely(!conn->socket)) { - static const struct smbdirect_socket_parameters zero_params; - - return &zero_params; + spin_lock(&server->srv_lock); + if (server->smbd_conn && server->smbd_conn->socket) { + sc = server->smbd_conn->socket; + smbdirect_socket_get(sc); } + spin_unlock(&server->srv_lock); - return smbdirect_socket_get_current_parameters(conn->socket); + return sc; +} + +static void smbd_put_socket(struct smbdirect_socket *sc) +{ + if (sc) + smbdirect_socket_put(sc); +} + +/* + * We can't return a pointer into the socket, as it may + * be destroyed at any time, so we copy the parameters + * into server->smbd_params and return a pointer to that. + * + * The copy is done under server->srv_lock, which also + * prevents server->smbd_conn from being destroyed, + * see smbd_destroy(). + * + * If there's no connection (anymore), we fill server->smbd_params + * with zeros. + */ +const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server) +{ + spin_lock(&server->srv_lock); + if (server->smbd_conn && server->smbd_conn->socket) + server->smbd_params = + *smbdirect_socket_get_current_parameters(server->smbd_conn->socket); + else + memset(&server->smbd_params, 0, sizeof(server->smbd_params)); + spin_unlock(&server->srv_lock); + + return &server->smbd_params; } struct smbd_connection *smbd_get_connection( @@ -392,6 +436,9 @@ struct smbd_connection *smbd_get_connection( if (!ret) return NULL; + /* + * ret is not published in server->smbd_conn yet + */ sp = smbdirect_socket_get_current_parameters(ret->socket); server->rdma_readwrite_threshold = @@ -437,7 +484,13 @@ int smbd_send(struct TCP_Server_Info *server, { struct smbd_connection *info = server->smbd_conn; struct smbdirect_socket *sc = info->socket; - const struct smbdirect_socket_parameters *sp = smbd_get_parameters(server); + /* + * We're called under cifs_server_lock(), so + * server->smbd_conn can't be destroyed while + * we use it. + */ + const struct smbdirect_socket_parameters *sp = + smbdirect_socket_get_current_parameters(sc); struct smb_rqst *rqst; struct iov_iter iter; struct smbdirect_send_batch_storage bstorage; @@ -541,13 +594,19 @@ struct smbdirect_mr_io *smbd_register_mr(struct TCP_Server_Info *server, struct iov_iter *iter, bool writing, bool need_invalidate) { - struct smbd_connection *info = server->smbd_conn; - struct smbdirect_socket *sc = info->socket; + struct smbdirect_socket *sc = smbd_get_socket(server); + struct smbdirect_mr_io *mr = NULL; - if (!smbdirect_connection_is_connected(sc)) - return NULL; + /* + * The mr has its own reference counting + * and doesn't need the socket reference + * once registered. + */ + if (sc && smbdirect_connection_is_connected(sc)) + mr = smbdirect_connection_register_mr_io(sc, iter, writing, need_invalidate); + smbd_put_socket(sc); - return smbdirect_connection_register_mr_io(sc, iter, writing, need_invalidate); + return mr; } void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr, @@ -572,7 +631,15 @@ void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m) if (!server->rdma) return; + /* + * We're called with cifs_tcp_ses_lock held, so we + * can't use smbd_get_socket(), but holding + * server->srv_lock prevents server->smbd_conn + * from being destroyed, see smbd_destroy(). + */ + spin_lock(&server->srv_lock); if (!server->smbd_conn) { + spin_unlock(&server->srv_lock); seq_puts(m, "\nSMBDirect transport not available"); return; } @@ -580,6 +647,7 @@ void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m) smbdirect_connection_legacy_debug_proc_show(server->smbd_conn->socket, server->rdma_readwrite_threshold, m); + spin_unlock(&server->srv_lock); } MODULE_IMPORT_NS("SMBDIRECT"); -- 2.43.0 From metze at samba.org Mon Oct 5 19:40:45 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 21:40:45 +0200 Subject: [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races Message-ID: Hi Namjae, These fix a set of problems in the shared smbdirect module (fs/smb/smbdirect/), reached through the listener/accept path, which in practice is driven by ksmbd for incoming SMB Direct connections. The main one is a pre-authentication remote denial of service: the listener keeps every accepted connection on a fixed-size backlog (listen.pending, limit 10 for ksmbd). A connection is only removed from that backlog on the success path (promotion to listen.ready after a completed negotiate, or dequeue by accept()). A connection that is accepted at the RDMA level but then fails before it is accepted by the upper layer - negotiate timeout, peer disconnect, or an invalid negotiate request - is never removed, so its backlog slot is leaked. After about ten such events the listener rejects every new connection with -EBUSY and SMB Direct stays unusable until the service is restarted. An unauthenticated peer can trigger this with ~10 aborted connections. This was reported by ???????? : https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ The series: - smbdirect_socket_destroy_sync() no longer waits for RDMA_CM_EVENT_DISCONNECTED after rdma_disconnect(). That wait could take very long (until the rdma cm gives up, e.g. a peer that just vanished) or never complete if the event already happened and the status was overwritten. rdma_destroy_id() in smbdirect_socket_destroy() is enough to stop further events; the rest of the disconnect protocol is handled by the rdma core asynchronously. This also makes releasing orphaned sockets (next patch) non-blocking. - A failed, not-yet-accepted child of a listener now moves itself to a new listen.orphaned list at the end of its cleanup work and queues listen.purge_orphaned_work, which releases it, so it no longer leaks its backlog slot (nor its struct sock / RDMA resources) for the lifetime of the listener. sc->accept.listener is only changed under listener->listen.lock, and whoever clears it owns the release; the listener is freed via kfree_rcu() so a child can dereference it under rcu_read_lock(). This is the DoS fix. - smbdirect_socket_accept() no longer hands out a socket that failed after it was put on the ready list (e.g. the peer disconnected in the meantime); it checks first_error / the status under listen.lock, orphans such a socket and tries the next one. I tested the reproducer and the problem is fixed and I run various xfstests. I think these are important and should go into 7.3 Changes since v1: (https://lore.kernel.org/r/cover.1791224972.git.metze at samba.org) - "smb: smbdirect: release failed pending sockets of a listener": fix a use-after-free and a missed-orphan race in smbdirect_listen_connect_request(), spotted by Sashiko. The nsc->first_error check and the orphaning are now done under listen.lock, together with setting nsc->accept.listener, so a concurrent smbdirect_socket_cleanup_work()/purge_orphaned_work() can neither free nsc while we still look at it, nor miss the orphaning and leak it on the pending list. - "smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept()": bound the accept() wait across the "skip a failed socket and try the next one" retries, also spotted by Sashiko. smbdirect_socket_wait_for_accept() now returns the remaining timeout and smbdirect_socket_accept() carries it over, so a stream of failed connections can no longer reset the caller's timeout. ksmbd passes MAX_SCHEDULE_TIMEOUT, so it is unaffected in practice. - Added Reported-by:/Closes: for the Sashiko reviews. Stefan Metzmacher (3): smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() smb: smbdirect: release failed pending sockets of a listener smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() fs/smb/smbdirect/accept.c | 138 ++++++++++++++++++++++++++-------- fs/smb/smbdirect/connection.c | 12 +-- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 124 ++++++++++++++++++++++++++++-- fs/smb/smbdirect/socket.c | 113 +++++++++++++++++++++++++--- fs/smb/smbdirect/socket.h | 37 +++++++++ 6 files changed, 373 insertions(+), 53 deletions(-) -- 2.43.0 From metze at samba.org Mon Oct 5 19:40:46 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 21:40:46 +0200 Subject: [PATCH v2 1/3] smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() In-Reply-To: References: Message-ID: <22ab29ca3288e96cf92d4991c32b8975297d9a28.1791229120.git.metze@samba.org> smbdirect_socket_destroy_sync() waited for RDMA_CM_EVENT_DISCONNECTED after smbdirect_socket_cleanup_work() called rdma_disconnect(). That can take very long, e.g. if the peer just disappeared, until the rdma cm gives up, or forever if the event already happened, but the status was overwritten afterwards. This is not needed: smbdirect_socket_destroy() drains the qp under rdma_lock_handler(), destroys it and calls rdma_destroy_id(), which only waits for a currently running event handler and makes sure no further events are delivered. The rest of the disconnect protocol (DREQ/DREP and timewait for IB, abrupt close for iWarp) is handled by the rdma core asynchronously. drivers/nvme/host/rdma.c also just calls rdma_disconnect() and ib_drain_qp() before rdma_destroy_id(). So smbdirect_socket_destroy() now also accepts SMBDIRECT_SOCKET_DISCONNECTING and changes the status to SMBDIRECT_SOCKET_DISCONNECTED itself under rdma_lock_handler(). We could still get RDMA_CM_EVENT_DISCONNECTED in the small windows between rdma_unlock_handler() and rdma_destroy_id(), but in that case smbdirect_connection_rdma_event_handler() is basically a no-op. Fixes: 422a2436697d ("smb: smbdirect: introduce smbdirect_socket_destroy[_sync]()") Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Cc: linux-rdma at vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/connection.c | 12 ++++---- fs/smb/smbdirect/socket.c | 56 ++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index afd31fa12a36..f48c857bda5a 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -83,9 +83,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup[_status]() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. */ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL) smbdirect_socket_schedule_cleanup_status(sc, @@ -113,9 +113,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup_status() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. * * This is also a normal disconnect so * SMBDIRECT_LOG_INFO should be good enough diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index bb02df6158b9..c36cb7cc0088 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -511,7 +511,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->status == SMBDIRECT_SOCKET_DESTROYED) return; - WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED, + /* + * smbdirect_socket_destroy_sync() doesn't wait + * for RDMA_CM_EVENT_DISCONNECTED, so we may still + * be in SMBDIRECT_SOCKET_DISCONNECTING + * (or already reached SMBDIRECT_SOCKET_DISCONNECTED) + */ + WARN_ONCE(sc->status < SMBDIRECT_SOCKET_DISCONNECTING, "status=%s first_error=%1pe", smbdirect_socket_status_string(sc->status), SMBDIRECT_DEBUG_ERR_PTR(sc->first_error)); @@ -542,6 +548,32 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); + /* + * We hold the handler lock, so the rdma event + * handlers can't change the status anymore. + * + * If RDMA_CM_EVENT_DISCONNECTED didn't arrive yet, + * we just stop waiting for it here. + * + * We already disabled disconnect_work above + * and before we call rdma_unlock_handler below + * we call smbdirect_connection_destroy_qp which + * sets sc->ib.qp = NULL. + * + * Between rdma_unlock_handler() and + * rdma_destroy_id() below there's a small + * windows where RDMA_CM_EVENT_DISCONNECTED + * could still arrive. + * + * But smbdirect_connection_rdma_event_handler + * will be a noop when calling smbdirect_socket_schedule_cleanup* + * ib_drain_qp() also won't be called. + */ + if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { + sc->status = SMBDIRECT_SOCKET_DISCONNECTED; + smbdirect_socket_wake_up_all(sc); + } + if (sc->ib.qp) { smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, "drain qp\n"); @@ -679,17 +711,21 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc) "destroying rdma session\n"); if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING) smbdirect_socket_cleanup_work(&sc->disconnect_work); - if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "wait for transport being disconnected\n"); - wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED); - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "waited for transport being disconnected\n"); - } /* - * Once we reached SMBDIRECT_SOCKET_DISCONNECTED, - * we should call smbdirect_socket_destroy() + * We don't wait for RDMA_CM_EVENT_DISCONNECTED, + * rdma_disconnect() was already called by + * smbdirect_socket_cleanup_work() if needed + * and smbdirect_socket_destroy() drains the qp, + * destroys it and calls rdma_destroy_id(), which + * only waits for a currently running event handler + * and makes sure no further events are delivered. + * The rest of the disconnect protocol is handled + * by the rdma core asynchronously. + * + * Waiting for RDMA_CM_EVENT_DISCONNECTED could + * take very long or forever, e.g. if the peer + * just disappeared. */ smbdirect_socket_destroy(sc); smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, -- 2.43.0 From metze at samba.org Mon Oct 5 19:40:47 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 21:40:47 +0200 Subject: [PATCH v2 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: A child of a listener only left the listen.pending or listen.ready list via smbdirect_socket_accept() or when the listener was destroyed. So every connection that failed before it was accepted (negotiate timeout, peer disconnect, invalid negotiate request, ...) stayed on the list and counted against the backlog until the listener was destroyed. After about backlog (10 for ksmbd) such connections smbdirect_listen_connect_request() rejected every new connection with -EBUSY, so an unauthenticated peer was able to permanently stop ksmbd from accepting SMB-Direct connections. Now a failed child moves itself to a new listen.orphaned list at the end of smbdirect_socket_cleanup_work(), after the disconnect was started, and queues listen.purge_orphaned_work, which releases it with smbdirect_socket_release(), without holding the listener's rdma_lock_handler() lock, in the same way smbdirect_socket_destroy() does for the remaining children. Orphaned sockets still count against the backlog until they are released. sc->accept.listener is now only changed under listener->listen.lock and whoever clears it is responsible for releasing the socket: smbdirect_socket_accept(), purge_orphaned_work or the destroy of the listener. In order to make that work: - a struct smbdirect_socket that is a listener is now freed via kfree_rcu(), so that a child can dereference sc->accept.listener under rcu_read_lock(), even if the listener is released concurrently. - smbdirect_listen_connect_request() only sets nsc->accept.listener after smbdirect_accept_connect_request() succeeded, so that a failure within it can't let nsc orphan itself before we release it. - smbdirect_accept_negotiate_recv_work() no longer moves a socket to the ready list if it already failed or no longer belongs to the listener. Every accepting socket has a listener, so if sc->accept.listener is already cleared, it no longer sends a negotiate response, as the socket is about to be released. - smbdirect_socket_destroy() of the listener disables purge_orphaned_work and also releases the orphaned sockets. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: ???????? Closes: https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 67 +++++++++++++------ fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 124 ++++++++++++++++++++++++++++++++++-- fs/smb/smbdirect/socket.c | 57 ++++++++++++++++- fs/smb/smbdirect/socket.h | 37 +++++++++++ 5 files changed, 260 insertions(+), 27 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1b30ca8476c1..1da95cdd1ebd 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -302,6 +302,7 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, connect.work); struct smbdirect_socket_parameters *sp = &sc->parameters; + struct smbdirect_socket *lsc; struct smbdirect_recv_io *recv_io; struct smbdirect_negotiate_req *nreq; unsigned long flags; @@ -464,29 +465,55 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) */ sp->max_fragmented_send_size = max_fragmented_size; - if (sc->accept.listener) { - struct smbdirect_socket *lsc = sc->accept.listener; - unsigned long flags; + /* + * Every accepting socket was created by + * smbdirect_listen_connect_request() and has a + * listener. If it's already cleared, the listener + * (or its purge_orphaned_work) is responsible for + * releasing us, so we must not send a negotiate + * response. + * + * The memory of the listener is freed via kfree_rcu(), + * see smbdirect_listen_orphan_socket(). + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del(&sc->accept.list); - list_add_tail(&sc->accept.list, &lsc->listen.ready); + spin_lock_irqsave(&lsc->listen.lock, flags); + /* + * The listener (or its purge_orphaned_work) + * may have cleared sc->accept.listener in the + * meantime and is responsible for releasing us. + * + * If we already failed, we're either + * already on the orphaned list or + * smbdirect_socket_cleanup_work() will + * move us there. + * + * In both cases we must not move us + * to the ready list. + */ + if (sc->accept.listener == lsc && !READ_ONCE(sc->first_error)) { + list_move_tail(&sc->accept.list, &lsc->listen.ready); wake_up(&lsc->listen.wait_queue); - spin_unlock_irqrestore(&lsc->listen.lock, flags); - - /* - * smbdirect_socket_accept() will call - * smbdirect_accept_negotiate_finish(nsc, 0); - * - * So that we don't send the negotiation - * response that grants credits to the peer - * before the socket is accepted by the - * application. - */ - return; } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); - ntstatus = le32_to_cpu(STATUS_SUCCESS); + /* + * smbdirect_socket_accept() will call + * smbdirect_accept_negotiate_finish(nsc, 0); + * + * So that we don't send the negotiation + * response that grants credits to the peer + * before the socket is accepted by the + * application. + */ + return; not_supported: smbdirect_accept_negotiate_finish(sc, ntstatus); @@ -839,7 +866,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct smbdirect_socket, accept.list); if (nsc) { - nsc->accept.listener = NULL; + WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); arg->is_empty = list_empty_careful(&lsc->listen.ready); } diff --git a/fs/smb/smbdirect/internal.h b/fs/smb/smbdirect/internal.h index e9959e6dc13a..69d0c9f899de 100644 --- a/fs/smb/smbdirect/internal.h +++ b/fs/smb/smbdirect/internal.h @@ -74,6 +74,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc); +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc); + int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc, enum smbdirect_socket_status expected_status, int unexpected_errno, diff --git a/fs/smb/smbdirect/listen.c b/fs/smb/smbdirect/listen.c index 2f78bcaedbf8..eba8dd45cd6d 100644 --- a/fs/smb/smbdirect/listen.c +++ b/fs/smb/smbdirect/listen.c @@ -10,6 +10,78 @@ static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event); +/* + * This is called by a socket that failed while it + * is still on the pending or ready list of its + * listener, typically at the end of + * smbdirect_socket_cleanup_work(), when the + * disconnect was already started. + * + * It moves itself to the orphaned list and + * lets smbdirect_listen_purge_orphaned_work() + * release it. Otherwise it would stay on the + * pending or ready list until the listener is + * destroyed and fill up the backlog, so that + * no new connections would be accepted. + * + * It's fine to call this more than once. + */ +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc) +{ + struct smbdirect_socket *lsc; + unsigned long flags; + + /* + * The memory of the listener is freed via + * kfree_rcu(), so it's safe to dereference it + * under rcu_read_lock(), even if sc->accept.listener + * is cleared and the listener is released concurrently. + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } + + spin_lock_irqsave(&lsc->listen.lock, flags); + if (sc->accept.listener == lsc) { + list_move_tail(&sc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); +} + +static void smbdirect_listen_purge_orphaned_work(struct work_struct *work) +{ + struct smbdirect_socket *lsc = + container_of(work, struct smbdirect_socket, listen.purge_orphaned_work); + struct smbdirect_socket *psc, *tsc; + LIST_HEAD(orphaned_list); + unsigned long flags; + + /* + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them. + */ + spin_lock_irqsave(&lsc->listen.lock, flags); + list_splice_tail_init(&lsc->listen.orphaned, &orphaned_list); + list_for_each_entry(psc, &orphaned_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); + spin_unlock_irqrestore(&lsc->listen.lock, flags); + + /* + * We don't hold the listener's rdma_lock_handler() + * lock here, see smbdirect_socket_destroy() + * for why that's important. + */ + list_for_each_entry_safe(psc, tsc, &orphaned_list, accept.list) { + list_del_init(&psc->accept.list); + smbdirect_socket_release(psc); + } +} + int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) { int ret; @@ -47,6 +119,9 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler; rdma_unlock_handler(sc->rdma.cm_id); + INIT_WORK(&sc->listen.purge_orphaned_work, + smbdirect_listen_purge_orphaned_work); + ret = rdma_listen(sc->rdma.cm_id, backlog); if (ret) { sc->first_error = ret; @@ -214,6 +289,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, size_t backlog = max_t(size_t, 1, lsc->listen.backlog); size_t psockets; size_t rsockets; + size_t osockets; int ret; if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) { @@ -254,14 +330,21 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, spin_lock_irqsave(&lsc->listen.lock, flags); psockets = list_count_nodes(&lsc->listen.pending); rsockets = list_count_nodes(&lsc->listen.ready); + /* + * Orphaned sockets still count against + * the backlog until they are released by + * smbdirect_listen_purge_orphaned_work(). + */ + osockets = list_count_nodes(&lsc->listen.orphaned); spin_unlock_irqrestore(&lsc->listen.lock, flags); if (psockets > backlog || rsockets > backlog || - (psockets + rsockets) > backlog) { + osockets > backlog || + (psockets + rsockets + osockets) > backlog) { smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR, - "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n", - lsc->listen.backlog, backlog, psockets, rsockets); + "Backlog[%d][%zu] full pending[%zu] ready[%zu] orphaned[%zu]\n", + lsc->listen.backlog, backlog, psockets, rsockets, osockets); return -EBUSY; } @@ -279,21 +362,52 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, if (ret) goto set_settings_failed; + /* + * Note that nsc->accept.listener is only set + * after smbdirect_accept_connect_request() + * succeeded, so that a failure within it + * can't let nsc orphan itself via + * smbdirect_listen_orphan_socket(), as we + * release it below in that case. + * + * The listener's handler_mutex is held while + * we're called, so smbdirect_socket_destroy() + * of the listener can't see nsc on the + * pending list before we're done. + */ spin_lock_irqsave(&lsc->listen.lock, flags); list_add_tail(&nsc->accept.list, &lsc->listen.pending); - nsc->accept.listener = lsc; spin_unlock_irqrestore(&lsc->listen.lock, flags); ret = smbdirect_accept_connect_request(nsc, &event->param.conn); if (ret) goto accept_connect_failed; + spin_lock_irqsave(&lsc->listen.lock, flags); + WRITE_ONCE(nsc->accept.listener, lsc); + /* + * If nsc already failed before we set accept.listener, its + * smbdirect_socket_cleanup_work() ran smbdirect_listen_orphan_socket() + * with accept.listener still NULL and could not orphan it, so we do + * it here. Doing this under listen.lock, together with setting + * accept.listener, means it can't race with a concurrent + * smbdirect_socket_cleanup_work(): either we observe first_error and + * orphan nsc, or cleanup_work observes accept.listener and orphans it. + * It also keeps nsc alive while we touch it, as + * smbdirect_listen_purge_orphaned_work() needs listen.lock to remove + * and release it. + */ + if (READ_ONCE(nsc->first_error)) { + list_move_tail(&nsc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + return 0; accept_connect_failed: spin_lock_irqsave(&lsc->listen.lock, flags); list_del_init(&nsc->accept.list); - nsc->accept.listener = NULL; spin_unlock_irqrestore(&lsc->listen.lock, flags); set_settings_failed: set_params_failed: diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index c36cb7cc0088..e270e36b3145 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -319,6 +319,15 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, * (smbdirect_socket_destroy) to reap. */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -427,6 +436,15 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * instances of one class -- harmless, but lockdep cannot tell). */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -486,6 +504,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * in order to notice the broken connection. */ smbdirect_socket_wake_up_all(sc); + + /* + * If we're still on the pending or ready list + * of a listener, we started the disconnect + * as far as possible above, so we move ourself + * to the orphaned list of the listener, + * which will release us. + * + * This is a no-op internally if + * sc->accept.listener is NULL. + */ + smbdirect_listen_orphan_socket(sc); } static void smbdirect_socket_destroy(struct smbdirect_socket *sc) @@ -544,6 +574,7 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) disable_work_sync(&sc->recv_io.posted.refill_work); disable_work_sync(&sc->idle.immediate_work); disable_delayed_work_sync(&sc->idle.timer_work); + disable_work_sync(&sc->listen.purge_orphaned_work); if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); @@ -607,6 +638,19 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) spin_lock_irqsave(&sc->listen.lock, flags); list_splice_tail_init(&sc->listen.ready, &pending_list); list_splice_tail_init(&sc->listen.pending, &pending_list); + /* + * purge_orphaned_work is already disabled above, + * so we also need to release the orphaned sockets. + * + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them and + * prevents them from moving themselves to + * the orphaned list via + * smbdirect_listen_orphan_socket(). + */ + list_splice_tail_init(&sc->listen.orphaned, &pending_list); + list_for_each_entry(psc, &pending_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); spin_unlock_irqrestore(&sc->listen.lock, flags); /* It's not possible for upper layer to get to reassembly */ @@ -650,7 +694,6 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) "release %zu pending sockets\n", psockets); list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) { list_del_init(&psc->accept.list); - psc->accept.listener = NULL; smbdirect_socket_release(psc); } if (sc->listen.backlog != -1) /* was a listener */ @@ -777,7 +820,17 @@ static void smbdirect_socket_release_destroy(struct kref *kref) * in DESTROYED state, before we free the memory. */ smbdirect_socket_destroy_sync(sc); - kfree(sc); + + /* + * Only a listener (backlog != -1) is ever dereferenced + * via sc->accept.listener under rcu_read_lock(), see + * smbdirect_listen_orphan_socket(). Other sockets can be + * freed immediately. + */ + if (sc->listen.backlog != -1) /* was a listener */ + kfree_rcu(sc, refs.rcu); + else + kfree(sc); } void smbdirect_socket_release(struct smbdirect_socket *sc) diff --git a/fs/smb/smbdirect/socket.h b/fs/smb/smbdirect/socket.h index c09eddd8ad16..36623980ec17 100644 --- a/fs/smb/smbdirect/socket.h +++ b/fs/smb/smbdirect/socket.h @@ -151,6 +151,12 @@ struct smbdirect_socket { * the disconnect refcount. */ struct kref destroy; + + /* + * smbdirect_socket_release_destroy() uses + * kfree_rcu(), see accept.listener. + */ + struct rcu_head rcu; } refs; /* RDMA related */ @@ -216,6 +222,16 @@ struct smbdirect_socket { * only be > 0. */ int backlog; + /* + * Sockets on pending or ready that failed + * move themselves to orphaned and queue + * purge_orphaned_work, which releases them. + * So that they are freed while the listener + * is still alive. They still count against + * the backlog until they are released. + */ + struct list_head orphaned; + struct work_struct purge_orphaned_work; } listen; /* @@ -226,7 +242,25 @@ struct smbdirect_socket { * connection. */ struct { + /* + * This is only set, protected by + * listener->listen.lock, while the socket + * is owned by the listener (on its pending, + * ready or orphaned list). The one who clears + * it is responsible for releasing the socket. + * + * The memory of a struct smbdirect_socket is + * freed via kfree_rcu(), so the listener + * can be dereferenced under rcu_read_lock(), + * even if accept.listener is cleared and + * the listener is released concurrently. + */ struct smbdirect_socket *listener; + /* + * Note even with listener being NULL, + * the socket can be on listeners.listen.pending + * before smbdirect_accept_connect_request() finished. + */ struct list_head list; } accept; @@ -588,6 +622,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) spin_lock_init(&sc->listen.lock); INIT_LIST_HEAD(&sc->listen.pending); INIT_LIST_HEAD(&sc->listen.ready); + INIT_LIST_HEAD(&sc->listen.orphaned); + INIT_WORK(&sc->listen.purge_orphaned_work, __smbdirect_socket_disabled_work); + disable_work_sync(&sc->listen.purge_orphaned_work); sc->listen.backlog = -1; /* not a listener */ init_waitqueue_head(&sc->listen.wait_queue); -- 2.43.0 From metze at samba.org Mon Oct 5 19:40:48 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 21:40:48 +0200 Subject: [PATCH v2 3/3] smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() In-Reply-To: References: Message-ID: <44a0c706a7722d8b776266661129857eeea76a1e.1791229120.git.metze@samba.org> A socket on the ready list is in SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, but it may fail before smbdirect_socket_accept() picks it up, e.g. RDMA_CM_EVENT_DISCONNECTED already moved it to SMBDIRECT_SOCKET_DISCONNECTED. smbdirect_socket_accept() unconditionally overwrote the status with SMBDIRECT_SOCKET_CONNECTED and handed out the already disconnected socket. smbdirect_socket_cleanup_work() then called rdma_disconnect() on it again, and before the recent commit smbdirect_socket_destroy_sync() waited forever for an RDMA_CM_EVENT_DISCONNECTED that already happened. Now we check first_error and change the status to SMBDIRECT_SOCKET_CONNECTED only via cmpxchg() from SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, while we still hold listen.lock. If the socket already failed, we move it to the orphaned list and queue purge_orphaned_work, like smbdirect_listen_orphan_socket() does, and try the next one. If we only found failed sockets, we wait for the next one. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 71 +++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1da95cdd1ebd..d901e240599f 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -826,7 +826,11 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long if (ret < 0) return ret; - return 0; + /* + * Return the remaining timeout, so the caller can carry it + * over to the next smbdirect_socket_wait_for_accept() call. + */ + return ret; } struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, @@ -834,8 +838,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct proto_accept_arg *arg) { struct smbdirect_socket *nsc; + bool orphaned; unsigned long flags; +again: + orphaned = false; + if (lsc->status != SMBDIRECT_SOCKET_LISTENING) { arg->err = -EINVAL; return NULL; @@ -847,7 +855,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } if (list_empty_careful(&lsc->listen.ready)) { - int ret; + long ret; if (timeo == 0) { arg->err = -EAGAIN; @@ -855,16 +863,51 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } ret = smbdirect_socket_wait_for_accept(lsc, timeo); - if (ret) { + if (ret < 0) { arg->err = ret; return NULL; } + /* + * Carry the remaining timeout over, so that a stream of + * failed connections that we orphan and skip (goto again) + * can't reset the caller's timeout and wait forever. + */ + timeo = ret; } spin_lock_irqsave(&lsc->listen.lock, flags); - nsc = list_first_entry_or_null(&lsc->listen.ready, - struct smbdirect_socket, - accept.list); + while ((nsc = list_first_entry_or_null(&lsc->listen.ready, + struct smbdirect_socket, + accept.list))) { + /* + * nsc may have failed after it was moved + * to the ready list, e.g. RDMA_CM_EVENT_DISCONNECTED + * already moved it to SMBDIRECT_SOCKET_DISCONNECTED. + * We must not overwrite that with + * SMBDIRECT_SOCKET_CONNECTED and hand + * out an already disconnected socket. + * + * Doing this under listen.lock means nsc + * still belongs to us, so we can just move + * a failed socket to the orphaned list, + * like smbdirect_listen_orphan_socket() does, + * and try the next one. + */ + if (!READ_ONCE(nsc->first_error) && + cmpxchg(&nsc->status, + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, + SMBDIRECT_SOCKET_CONNECTED) == + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) + break; + + smbdirect_log_rdma_event(nsc, SMBDIRECT_LOG_INFO, + "orphaning failed socket status=%s first_error=%1pe\n", + smbdirect_socket_status_string(nsc->status), + SMBDIRECT_DEBUG_ERR_PTR(nsc->first_error)); + list_move_tail(&nsc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + orphaned = true; + } if (nsc) { WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); @@ -872,6 +915,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } spin_unlock_irqrestore(&lsc->listen.lock, flags); if (!nsc) { + /* + * If we only found failed sockets, + * we wait for the next one. + */ + if (orphaned) + goto again; arg->err = -EAGAIN; return NULL; } @@ -882,12 +931,14 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, * so it didn't grant any credits to us. * * The caller expects a connected socket - * now as there are no credits anyway. + * now as there are no credits anyway, + * above we already changed to SMBDIRECT_SOCKET_CONNECTED + * under the lsc->listen.lock and with cmpxchg. * - * Then we send the negotiation response in - * order to grant credits to the peer. + * Now we send the negotiation response in + * order to grant credits to the peer, + * as the socket is now visible to the application layer. */ - nsc->status = SMBDIRECT_SOCKET_CONNECTED; smbdirect_accept_negotiate_finish(nsc, 0); return nsc; -- 2.43.0 From metze at samba.org Mon Oct 5 19:42:54 2026 From: metze at samba.org (Stefan Metzmacher) Date: Mon, 5 Oct 2026 21:42:54 +0200 Subject: [PATCH 0/5] smb: client: don't use server->smbd_conn without a reference or lock In-Reply-To: References: Message-ID: <1111a3da-3eb7-44e0-8c68-4a09bb4615a1@samba.org> Hi Paulo/Namjae, I'll address these https://sashiko.dev/#/patchset/cover.1791225453.git.metze%40samba.org and resubmit v2 later... metze > The SMB Direct client keeps the transport in server->smbd_conn and > accesses it from several places without any locking: > > - smbd_get_parameters() > - smbd_register_mr() > - smbd_debug_proc_show() > > At the same time cifs_abort_connection() (and the reconnect path) can > call smbd_destroy() at any time, which frees the smbd_connection and > releases the underlying smbdirect socket. So the readers above can race > with the teardown and end up using a freed connection/socket, a > use-after-free. > > While looking at this, smbd_get_parameters() also turned out to > dereference server->smbd_conn without checking it, which is a NULL > pointer dereference in smb2_negotiate_wsize()/smb2_negotiate_rsize() > when it is called during a reconnect window. > > This series closes the races: > > - server->smbd_conn is only ever changed under server->srv_lock, so > readers can safely look at it while holding that lock. > > - smbdirect gets smbdirect_socket_get()/smbdirect_socket_put(), which > take and drop a reference that keeps the socket memory alive (but > does not keep it connected). smbd_register_mr() uses them to hold > the socket across smbdirect_connection_register_mr_io(); once the > connection is gone the socket is no longer connected and the call > fails gracefully. A registered MR has its own reference counting. > > - smbd_get_parameters() no longer returns a pointer into the socket > (which cannot be kept valid for the callers). It copies the current > parameters into the new server->smbd_params under server->srv_lock > and returns a pointer to that; with no connection it returns zeros. > > - smbd_debug_proc_show() runs with cifs_tcp_ses_lock held, so it takes > server->srv_lock instead of a socket reference. > > The two "pass server to ..." patches are mechanical prototype changes > with no behaviour change, split out so the final fix stays small and > each step builds on its own. > > All of this is a use-after-free that goes back to the switch to the > smbdirect socket API, so the whole series is tagged for stable via > > Fixes: b8aef8c8808c ("smb: client: make use of smbdirect_socket_create_kern()/smbdirect_socket_release()") > > which is in mainline since v7.1-rc1. > > I run various xfstests with this. > > I'm unsure if this should go via cifs-next or ksmbd-for-next, > for consistency with the series I just send I guess both > could go via ksmbd-for-next? > > Stefan Metzmacher (5): > smb: client: change server->smbd_conn only under server->srv_lock > smb: smbdirect: add smbdirect_socket_get() and smbdirect_socket_put() > smb: client: pass server to smbd_get_parameters() > smb: client: pass server to smbd_register_mr() > smb: client: don't use server->smbd_conn without a reference or lock > > fs/smb/client/cifsglob.h | 12 ++++ > fs/smb/client/connect.c | 12 +++- > fs/smb/client/file.c | 4 +- > fs/smb/client/smb2ops.c | 4 +- > fs/smb/client/smb2pdu.c | 4 +- > fs/smb/client/smbdirect.c | 124 +++++++++++++++++++++++++++++++++----- > fs/smb/client/smbdirect.h | 8 ++- > fs/smb/smbdirect/socket.c | 27 +++++++++ > include/linux/smbdirect.h | 3 + > 9 files changed, 171 insertions(+), 27 deletions(-) > From linkinjeon at kernel.org Tue Oct 6 01:20:11 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Tue, 6 Oct 2026 10:20:11 +0900 Subject: [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races In-Reply-To: References: Message-ID: On Tue, Oct 6, 2026 at 9:19?AM ??? wrote: > > [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races > > Hi Stefan, > > Thanks a lot for the fix and for crediting me. > > Could you please use my real name instead of the display name in the > Reported-by: tags (patch 2/3) and in the cover letter, i.e.: > > Reported-by: Lee Seong Hyeon > > The address stays the same. If it is easier for you, I'm also fine with > you fixing it up when applying, or with Namjae doing it. Okay, I will directly update it when applying this patch-set. Thanks. From tom at talpey.com Tue Oct 6 01:34:08 2026 From: tom at talpey.com (Tom Talpey) Date: Mon, 5 Oct 2026 21:34:08 -0400 Subject: [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races In-Reply-To: References: Message-ID: <6981e5e5-e856-4613-a3fb-c8cdc7a9219d@talpey.com> FYI I am hoping to be able to test these and tge others Tuesday (tomorrow) to verify they fix the issues we saw at IOLab. Some were not available when the test network shut down. Tom. Oct 5, 2026 9:23:57 PM Namjae Jeon : > On Tue, Oct 6, 2026 at 9:19?AM ??? wrote: >> >> [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races >> >> Hi Stefan, >> >> Thanks a lot for the fix and for crediting me. >> >> Could you please use my real name instead of the display name in the >> Reported-by: tags (patch 2/3) and in the cover letter, i.e.: >> >> ? Reported-by: Lee Seong Hyeon >> >> The address stays the same. If it is easier for you, I'm also fine with >> you fixing it up when applying, or with Namjae doing it. > Okay, I will directly update it when applying this patch-set. > > Thanks. From metze at samba.org Tue Oct 6 10:22:25 2026 From: metze at samba.org (Stefan Metzmacher) Date: Tue, 6 Oct 2026 12:22:25 +0200 Subject: [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races In-Reply-To: References: Message-ID: <0c378d19-545e-4fd9-9f48-b00c6962ea59@samba.org> Hi Lee Seong Hyeon, > Thanks a lot for the fix and for crediting me. > > Could you please use my real name instead of the display name in the > Reported-by: tags (patch 2/3) and in the cover letter, i.e.: > > Reported-by: Lee Seong Hyeon > > The address stays the same. If it is easier for you, I'm also fine with > you fixing it up when applying, or with Namjae doing it. I'll update it in v3, there's still one thing sashiko doesn't like: https://sashiko.dev/#/patchset/cover.1791229120.git.metze%40samba.org I'll check what to do this that in the next days. Thanks! metze From metze at samba.org Tue Oct 6 19:08:54 2026 From: metze at samba.org (Stefan Metzmacher) Date: Tue, 6 Oct 2026 21:08:54 +0200 Subject: [PATCH v3 0/3] smb: smbdirect: fix listener backlog leak and teardown races Message-ID: Hi Namjae, These fix a set of problems in the shared smbdirect module (fs/smb/smbdirect/), reached through the listener/accept path, which in practice is driven by ksmbd for incoming SMB Direct connections. The main one is a pre-authentication remote denial of service: the listener keeps every accepted connection on a fixed-size backlog (listen.pending, limit 10 for ksmbd). A connection is only removed from that backlog on the success path (promotion to listen.ready after a completed negotiate, or dequeue by accept()). A connection that is accepted at the RDMA level but then fails before it is accepted by the upper layer - negotiate timeout, peer disconnect, or an invalid negotiate request - is never removed, so its backlog slot is leaked. After about ten such events the listener rejects every new connection with -EBUSY and SMB Direct stays unusable until the service is restarted. An unauthenticated peer can trigger this with ~10 aborted connections. This was reported by Lee Seong Hyeon : https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ The series: - smbdirect_socket_destroy_sync() no longer waits for RDMA_CM_EVENT_DISCONNECTED after rdma_disconnect(). That wait could take very long (until the rdma cm gives up, e.g. a peer that just vanished) or never complete if the event already happened and the status was overwritten. rdma_destroy_id() in smbdirect_socket_destroy() is enough to stop further events; the rest of the disconnect protocol is handled by the rdma core asynchronously. This also makes releasing orphaned sockets (next patch) non-blocking. - A failed, not-yet-accepted child of a listener now moves itself to a new listen.orphaned list at the end of its cleanup work and queues listen.purge_orphaned_work, which releases it, so it no longer leaks its backlog slot (nor its struct sock / RDMA resources) for the lifetime of the listener. sc->accept.listener is only changed under listener->listen.lock, and whoever clears it owns the release; the listener is freed via kfree_rcu() so a child can dereference it under rcu_read_lock(). This is the DoS fix. - smbdirect_socket_accept() no longer hands out a socket that failed after it was put on the ready list (e.g. the peer disconnected in the meantime); it checks first_error / the status under listen.lock, orphans such a socket and tries the next one. I tested the reproducer and the problem is fixed and I run various xfstests. I think these are important and should go into 7.3 Changes since v2: (https://lore.kernel.org/r/cover.1791229120.git.metze at samba.org/) - "smb: smbdirect: release failed pending sockets of a listener": reworked the connect-request error handling after more Sashiko review. smbdirect_accept_connect_request() no longer tears down any RDMA state or clears the cm_id on failure; it only returns a not-yet-posted recv_io to the pool and returns the error. smbdirect_listen_connect_request() now sets nsc->accept.listener and publishes nsc on the pending list *before* it calls smbdirect_accept_connect_request(), and on a non-zero return it schedules nsc's teardown with smbdirect_socket_schedule_cleanup() and always returns 0, so the rdma_cm core keeps the connection id and nsc owns it (and destroys it in its own deferred teardown). The teardown must be deferred because nsc's id_priv->handler_mutex is held by the rdma_cm core across the CONNECT_REQUEST handler; that same lock (and the listener's) also guarantees nsc can't go away under us. This closes the race Sashiko reported, where a connection that established between rdma_accept() and setting the listener could be left stranded on the pending list. Changes since v1: (https://lore.kernel.org/r/cover.1791224972.git.metze at samba.org) - "smb: smbdirect: release failed pending sockets of a listener": fix a use-after-free and a missed-orphan race in smbdirect_listen_connect_request(), spotted by Sashiko. The nsc->first_error check and the orphaning are now done under listen.lock, together with setting nsc->accept.listener, so a concurrent smbdirect_socket_cleanup_work()/purge_orphaned_work() can neither free nsc while we still look at it, nor miss the orphaning and leak it on the pending list. - "smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept()": bound the accept() wait across the "skip a failed socket and try the next one" retries, also spotted by Sashiko. smbdirect_socket_wait_for_accept() now returns the remaining timeout and smbdirect_socket_accept() carries it over, so a stream of failed connections can no longer reset the caller's timeout. ksmbd passes MAX_SCHEDULE_TIMEOUT, so it is unaffected in practice. - Added Reported-by:/Closes: for the Sashiko reviews. Stefan Metzmacher (3): smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() smb: smbdirect: release failed pending sockets of a listener smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() fs/smb/smbdirect/accept.c | 194 +++++++++++++++++++++++----------- fs/smb/smbdirect/connection.c | 12 +-- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 137 ++++++++++++++++++++++-- fs/smb/smbdirect/socket.c | 113 +++++++++++++++++--- fs/smb/smbdirect/socket.h | 42 ++++++++ 6 files changed, 411 insertions(+), 89 deletions(-) -- 2.43.0 From metze at samba.org Tue Oct 6 19:08:55 2026 From: metze at samba.org (Stefan Metzmacher) Date: Tue, 6 Oct 2026 21:08:55 +0200 Subject: [PATCH v3 1/3] smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() In-Reply-To: References: Message-ID: <22ab29ca3288e96cf92d4991c32b8975297d9a28.1791311817.git.metze@samba.org> smbdirect_socket_destroy_sync() waited for RDMA_CM_EVENT_DISCONNECTED after smbdirect_socket_cleanup_work() called rdma_disconnect(). That can take very long, e.g. if the peer just disappeared, until the rdma cm gives up, or forever if the event already happened, but the status was overwritten afterwards. This is not needed: smbdirect_socket_destroy() drains the qp under rdma_lock_handler(), destroys it and calls rdma_destroy_id(), which only waits for a currently running event handler and makes sure no further events are delivered. The rest of the disconnect protocol (DREQ/DREP and timewait for IB, abrupt close for iWarp) is handled by the rdma core asynchronously. drivers/nvme/host/rdma.c also just calls rdma_disconnect() and ib_drain_qp() before rdma_destroy_id(). So smbdirect_socket_destroy() now also accepts SMBDIRECT_SOCKET_DISCONNECTING and changes the status to SMBDIRECT_SOCKET_DISCONNECTED itself under rdma_lock_handler(). We could still get RDMA_CM_EVENT_DISCONNECTED in the small windows between rdma_unlock_handler() and rdma_destroy_id(), but in that case smbdirect_connection_rdma_event_handler() is basically a no-op. Fixes: 422a2436697d ("smb: smbdirect: introduce smbdirect_socket_destroy[_sync]()") Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Cc: linux-rdma at vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/connection.c | 12 ++++---- fs/smb/smbdirect/socket.c | 56 ++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index afd31fa12a36..f48c857bda5a 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -83,9 +83,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup[_status]() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. */ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL) smbdirect_socket_schedule_cleanup_status(sc, @@ -113,9 +113,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup_status() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. * * This is also a normal disconnect so * SMBDIRECT_LOG_INFO should be good enough diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index bb02df6158b9..c36cb7cc0088 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -511,7 +511,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->status == SMBDIRECT_SOCKET_DESTROYED) return; - WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED, + /* + * smbdirect_socket_destroy_sync() doesn't wait + * for RDMA_CM_EVENT_DISCONNECTED, so we may still + * be in SMBDIRECT_SOCKET_DISCONNECTING + * (or already reached SMBDIRECT_SOCKET_DISCONNECTED) + */ + WARN_ONCE(sc->status < SMBDIRECT_SOCKET_DISCONNECTING, "status=%s first_error=%1pe", smbdirect_socket_status_string(sc->status), SMBDIRECT_DEBUG_ERR_PTR(sc->first_error)); @@ -542,6 +548,32 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); + /* + * We hold the handler lock, so the rdma event + * handlers can't change the status anymore. + * + * If RDMA_CM_EVENT_DISCONNECTED didn't arrive yet, + * we just stop waiting for it here. + * + * We already disabled disconnect_work above + * and before we call rdma_unlock_handler below + * we call smbdirect_connection_destroy_qp which + * sets sc->ib.qp = NULL. + * + * Between rdma_unlock_handler() and + * rdma_destroy_id() below there's a small + * windows where RDMA_CM_EVENT_DISCONNECTED + * could still arrive. + * + * But smbdirect_connection_rdma_event_handler + * will be a noop when calling smbdirect_socket_schedule_cleanup* + * ib_drain_qp() also won't be called. + */ + if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { + sc->status = SMBDIRECT_SOCKET_DISCONNECTED; + smbdirect_socket_wake_up_all(sc); + } + if (sc->ib.qp) { smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, "drain qp\n"); @@ -679,17 +711,21 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc) "destroying rdma session\n"); if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING) smbdirect_socket_cleanup_work(&sc->disconnect_work); - if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "wait for transport being disconnected\n"); - wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED); - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "waited for transport being disconnected\n"); - } /* - * Once we reached SMBDIRECT_SOCKET_DISCONNECTED, - * we should call smbdirect_socket_destroy() + * We don't wait for RDMA_CM_EVENT_DISCONNECTED, + * rdma_disconnect() was already called by + * smbdirect_socket_cleanup_work() if needed + * and smbdirect_socket_destroy() drains the qp, + * destroys it and calls rdma_destroy_id(), which + * only waits for a currently running event handler + * and makes sure no further events are delivered. + * The rest of the disconnect protocol is handled + * by the rdma core asynchronously. + * + * Waiting for RDMA_CM_EVENT_DISCONNECTED could + * take very long or forever, e.g. if the peer + * just disappeared. */ smbdirect_socket_destroy(sc); smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, -- 2.43.0 From metze at samba.org Tue Oct 6 19:08:56 2026 From: metze at samba.org (Stefan Metzmacher) Date: Tue, 6 Oct 2026 21:08:56 +0200 Subject: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: A child of a listener only left the listen.pending or listen.ready list via smbdirect_socket_accept() or when the listener was destroyed. So every connection that failed before it was accepted (negotiate timeout, peer disconnect, invalid negotiate request, ...) stayed on the list and counted against the backlog until the listener was destroyed. After about backlog (10 for ksmbd) such connections smbdirect_listen_connect_request() rejected every new connection with -EBUSY, so an unauthenticated peer was able to permanently stop ksmbd from accepting SMB-Direct connections. Now a failed child moves itself to a new listen.orphaned list at the end of smbdirect_socket_cleanup_work(), after the disconnect was started, and queues listen.purge_orphaned_work, which releases it with smbdirect_socket_release(), without holding the listener's rdma_lock_handler() lock, in the same way smbdirect_socket_destroy() does for the remaining children. Orphaned sockets still count against the backlog until they are released. sc->accept.listener is now only changed under listener->listen.lock and whoever clears it is responsible for releasing the socket: smbdirect_socket_accept(), purge_orphaned_work or the destroy of the listener. In order to make that work: - a struct smbdirect_socket that is a listener is now freed via kfree_rcu(), so that a child can dereference sc->accept.listener under rcu_read_lock(), even if the listener is released concurrently. - smbdirect_listen_connect_request() adds nsc to the listen.pending list and sets nsc->accept.listener before it calls smbdirect_accept_connect_request(), and always returns 0 so the rdma_cm core keeps the connection id and nsc owns it. On a non-zero return it schedules nsc's teardown with smbdirect_socket_schedule_cleanup(), which orphans it off the listener so that purge_orphaned_work releases it; nsc destroys its own connection id with rdma_destroy_id() in that teardown. The teardown has to be deferred because the rdma_cm core holds nsc's id_priv->handler_mutex across the handler, so nsc->rdma.cm_id must not be destroyed synchronously from here. That same handler_mutex (and the listener's) also guarantees nsc can't go away under us, so setting the listener before the call is safe and lets a failure orphan nsc. - smbdirect_accept_negotiate_recv_work() no longer moves a socket to the ready list if it already failed or no longer belongs to the listener. Every accepting socket has a listener, so if sc->accept.listener is already cleared, it no longer sends a negotiate response, as the socket is about to be released. - smbdirect_socket_destroy() of the listener disables purge_orphaned_work and also releases the orphaned sockets. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: Lee Seong Hyeon Closes: https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Closes: https://sashiko.dev/#/patchset/cover.1791229120.git.metze%40samba.org Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 123 +++++++++++++++++++------------- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 137 +++++++++++++++++++++++++++++++++--- fs/smb/smbdirect/socket.c | 57 ++++++++++++++- fs/smb/smbdirect/socket.h | 42 +++++++++++ 5 files changed, 298 insertions(+), 63 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1b30ca8476c1..9353c7647735 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -19,15 +19,17 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, const struct rdma_conn_param *param) { struct smbdirect_socket_parameters *sp = &sc->parameters; - struct smbdirect_recv_io *recv_io; + struct smbdirect_recv_io *recv_io = NULL; u8 peer_initiator_depth; u8 peer_responder_resources; struct rdma_conn_param conn_param; __be32 ird_ord_hdr[2]; int ret; - if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED)) - return -EINVAL; + if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED)) { + ret = -EINVAL; + goto cleanup; + } /* * First set what the we as server are able to support @@ -48,7 +50,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_accept_init_params() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto init_params_failed; + goto cleanup; } ret = smbdirect_connection_create_qp(sc); @@ -56,7 +58,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_create_qp() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto create_qp_failed; + goto cleanup; } ret = smbdirect_connection_create_mem_pools(sc); @@ -64,7 +66,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_create_mem_pools() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto create_mem_failed; + goto cleanup; } recv_io = smbdirect_connection_get_recv_io(sc); @@ -73,7 +75,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_get_recv_io() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto get_recv_io_failed; + goto cleanup; } recv_io->cqe.done = smbdirect_accept_negotiate_recv_done; @@ -87,7 +89,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_post_recv_io() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto post_recv_io_failed; + goto cleanup; } /* * From here recv_io is known to the RDMA QP and needs ib_drain_qp and @@ -130,7 +132,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "rdma_accept() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto rdma_accept_failed; + goto cleanup; } /* @@ -144,31 +146,25 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, return 0; -rdma_accept_failed: +cleanup: /* - * The recv_io posted above is now owned by the QP (recv_io was set to - * NULL after a successful post). smbdirect_connection_destroy_qp() - * calls ib_drain_qp(), whose completion - * (smbdirect_accept_negotiate_recv_done) returns the recv_io to the - * free list via smbdirect_connection_put_recv_io(). It therefore MUST - * run BEFORE smbdirect_connection_destroy_mem_pools(): otherwise the - * posted recv_io is still outstanding when kmem_cache_destroy() runs - * ("Slab cache still has objects") and is later freed into an - * already-destroyed mempool (mempool_free_bulk NULL-ptr-deref). + * No RDMA state is torn down here, and in particular sc is not + * released: the caller (smbdirect_listen_connect_request()) owns that + * decision and schedules sc's teardown via + * smbdirect_socket_schedule_cleanup() on a non-zero return. All of the + * RDMA state - the QP, the memory pools and the cm_id itself - is then + * torn down asynchronously by smbdirect_socket_cleanup_work() -> + * smbdirect_socket_release() -> smbdirect_socket_destroy(), which + * drains the QP (returning any posted recv_io to the pool) before + * destroying the memory pools and destroys the cm_id with + * rdma_destroy_id(), all in the correct order. + * + * The only resource not reachable by that teardown is a recv_io that + * was taken from the pool but not yet posted to the QP (recv_io is set + * to NULL after a successful post), so hand it back here. */ - smbdirect_connection_destroy_qp(sc); - smbdirect_connection_destroy_mem_pools(sc); - return ret; -post_recv_io_failed: - /* post failed: recv_io was not accepted by the QP, still in hand */ if (recv_io) smbdirect_connection_put_recv_io(recv_io); -get_recv_io_failed: - smbdirect_connection_destroy_mem_pools(sc); -create_mem_failed: - smbdirect_connection_destroy_qp(sc); -create_qp_failed: -init_params_failed: return ret; } @@ -302,6 +298,7 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, connect.work); struct smbdirect_socket_parameters *sp = &sc->parameters; + struct smbdirect_socket *lsc; struct smbdirect_recv_io *recv_io; struct smbdirect_negotiate_req *nreq; unsigned long flags; @@ -464,29 +461,55 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) */ sp->max_fragmented_send_size = max_fragmented_size; - if (sc->accept.listener) { - struct smbdirect_socket *lsc = sc->accept.listener; - unsigned long flags; + /* + * Every accepting socket was created by + * smbdirect_listen_connect_request() and has a + * listener. If it's already cleared, the listener + * (or its purge_orphaned_work) is responsible for + * releasing us, so we must not send a negotiate + * response. + * + * The memory of the listener is freed via kfree_rcu(), + * see smbdirect_listen_orphan_socket(). + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del(&sc->accept.list); - list_add_tail(&sc->accept.list, &lsc->listen.ready); + spin_lock_irqsave(&lsc->listen.lock, flags); + /* + * The listener (or its purge_orphaned_work) + * may have cleared sc->accept.listener in the + * meantime and is responsible for releasing us. + * + * If we already failed, we're either + * already on the orphaned list or + * smbdirect_socket_cleanup_work() will + * move us there. + * + * In both cases we must not move us + * to the ready list. + */ + if (sc->accept.listener == lsc && !READ_ONCE(sc->first_error)) { + list_move_tail(&sc->accept.list, &lsc->listen.ready); wake_up(&lsc->listen.wait_queue); - spin_unlock_irqrestore(&lsc->listen.lock, flags); - - /* - * smbdirect_socket_accept() will call - * smbdirect_accept_negotiate_finish(nsc, 0); - * - * So that we don't send the negotiation - * response that grants credits to the peer - * before the socket is accepted by the - * application. - */ - return; } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); - ntstatus = le32_to_cpu(STATUS_SUCCESS); + /* + * smbdirect_socket_accept() will call + * smbdirect_accept_negotiate_finish(nsc, 0); + * + * So that we don't send the negotiation + * response that grants credits to the peer + * before the socket is accepted by the + * application. + */ + return; not_supported: smbdirect_accept_negotiate_finish(sc, ntstatus); @@ -839,7 +862,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct smbdirect_socket, accept.list); if (nsc) { - nsc->accept.listener = NULL; + WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); arg->is_empty = list_empty_careful(&lsc->listen.ready); } diff --git a/fs/smb/smbdirect/internal.h b/fs/smb/smbdirect/internal.h index e9959e6dc13a..69d0c9f899de 100644 --- a/fs/smb/smbdirect/internal.h +++ b/fs/smb/smbdirect/internal.h @@ -74,6 +74,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc); +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc); + int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc, enum smbdirect_socket_status expected_status, int unexpected_errno, diff --git a/fs/smb/smbdirect/listen.c b/fs/smb/smbdirect/listen.c index 2f78bcaedbf8..3c91f00d95ce 100644 --- a/fs/smb/smbdirect/listen.c +++ b/fs/smb/smbdirect/listen.c @@ -10,6 +10,78 @@ static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event); +/* + * This is called by a socket that failed while it + * is still on the pending or ready list of its + * listener, typically at the end of + * smbdirect_socket_cleanup_work(), when the + * disconnect was already started. + * + * It moves itself to the orphaned list and + * lets smbdirect_listen_purge_orphaned_work() + * release it. Otherwise it would stay on the + * pending or ready list until the listener is + * destroyed and fill up the backlog, so that + * no new connections would be accepted. + * + * It's fine to call this more than once. + */ +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc) +{ + struct smbdirect_socket *lsc; + unsigned long flags; + + /* + * The memory of the listener is freed via + * kfree_rcu(), so it's safe to dereference it + * under rcu_read_lock(), even if sc->accept.listener + * is cleared and the listener is released concurrently. + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } + + spin_lock_irqsave(&lsc->listen.lock, flags); + if (sc->accept.listener == lsc) { + list_move_tail(&sc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); +} + +static void smbdirect_listen_purge_orphaned_work(struct work_struct *work) +{ + struct smbdirect_socket *lsc = + container_of(work, struct smbdirect_socket, listen.purge_orphaned_work); + struct smbdirect_socket *psc, *tsc; + LIST_HEAD(orphaned_list); + unsigned long flags; + + /* + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them. + */ + spin_lock_irqsave(&lsc->listen.lock, flags); + list_splice_tail_init(&lsc->listen.orphaned, &orphaned_list); + list_for_each_entry(psc, &orphaned_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); + spin_unlock_irqrestore(&lsc->listen.lock, flags); + + /* + * We don't hold the listener's rdma_lock_handler() + * lock here, see smbdirect_socket_destroy() + * for why that's important. + */ + list_for_each_entry_safe(psc, tsc, &orphaned_list, accept.list) { + list_del_init(&psc->accept.list); + smbdirect_socket_release(psc); + } +} + int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) { int ret; @@ -47,6 +119,9 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler; rdma_unlock_handler(sc->rdma.cm_id); + INIT_WORK(&sc->listen.purge_orphaned_work, + smbdirect_listen_purge_orphaned_work); + ret = rdma_listen(sc->rdma.cm_id, backlog); if (ret) { sc->first_error = ret; @@ -214,6 +289,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, size_t backlog = max_t(size_t, 1, lsc->listen.backlog); size_t psockets; size_t rsockets; + size_t osockets; int ret; if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) { @@ -254,14 +330,21 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, spin_lock_irqsave(&lsc->listen.lock, flags); psockets = list_count_nodes(&lsc->listen.pending); rsockets = list_count_nodes(&lsc->listen.ready); + /* + * Orphaned sockets still count against + * the backlog until they are released by + * smbdirect_listen_purge_orphaned_work(). + */ + osockets = list_count_nodes(&lsc->listen.orphaned); spin_unlock_irqrestore(&lsc->listen.lock, flags); if (psockets > backlog || rsockets > backlog || - (psockets + rsockets) > backlog) { + osockets > backlog || + (psockets + rsockets + osockets) > backlog) { smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR, - "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n", - lsc->listen.backlog, backlog, psockets, rsockets); + "Backlog[%d][%zu] full pending[%zu] ready[%zu] orphaned[%zu]\n", + lsc->listen.backlog, backlog, psockets, rsockets, osockets); return -EBUSY; } @@ -279,22 +362,54 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, if (ret) goto set_settings_failed; + /* + * Publish nsc on the pending list with accept.listener set before + * smbdirect_accept_connect_request() calls rdma_accept(). Once the + * connection can establish, smbdirect_accept_negotiate_recv_work() + * may run and it must observe accept.listener, otherwise nsc would + * be left stranded on the pending list forever. + * + * The listener's handler_mutex is held while we're called, so + * smbdirect_socket_destroy() of the listener can't reach nsc on the + * pending list before we're done. + * + * From here nsc is published on the pending list: on failure of + * smbdirect_accept_connect_request() below we schedule nsc's teardown, + * which orphans nsc off the listener so + * smbdirect_listen_purge_orphaned_work() -> smbdirect_socket_release() + * releases it. + */ spin_lock_irqsave(&lsc->listen.lock, flags); list_add_tail(&nsc->accept.list, &lsc->listen.pending); - nsc->accept.listener = lsc; + WRITE_ONCE(nsc->accept.listener, lsc); spin_unlock_irqrestore(&lsc->listen.lock, flags); ret = smbdirect_accept_connect_request(nsc, &event->param.conn); - if (ret) - goto accept_connect_failed; + if (ret) { + /* + * The rdma_cm core holds both the listener's and nsc's + * id_priv->handler_mutex across this CONNECT_REQUEST handler, + * so neither can go away under us here and nsc cannot receive + * any rdma event: it is safe to hand nsc to its teardown. + * + * That teardown must be deferred though: nsc->rdma.cm_id + * (= new_id) has its handler_mutex held by us, so it cannot be + * destroyed synchronously from here. + * smbdirect_socket_schedule_cleanup() only queues work; + * smbdirect_socket_cleanup_work() then orphans nsc off the + * listener and smbdirect_listen_purge_orphaned_work() releases + * it, destroying nsc's cm_id with rdma_destroy_id(). + */ + smbdirect_socket_schedule_cleanup(nsc, ret); + } + /* + * Always return 0 so the rdma_cm core keeps new_id: nsc owns it now. + * On success it stays connected; on failure its deferred teardown + * above destroys it. + */ return 0; -accept_connect_failed: - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del_init(&nsc->accept.list); - nsc->accept.listener = NULL; - spin_unlock_irqrestore(&lsc->listen.lock, flags); set_settings_failed: set_params_failed: /* diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index c36cb7cc0088..e270e36b3145 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -319,6 +319,15 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, * (smbdirect_socket_destroy) to reap. */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -427,6 +436,15 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * instances of one class -- harmless, but lockdep cannot tell). */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -486,6 +504,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * in order to notice the broken connection. */ smbdirect_socket_wake_up_all(sc); + + /* + * If we're still on the pending or ready list + * of a listener, we started the disconnect + * as far as possible above, so we move ourself + * to the orphaned list of the listener, + * which will release us. + * + * This is a no-op internally if + * sc->accept.listener is NULL. + */ + smbdirect_listen_orphan_socket(sc); } static void smbdirect_socket_destroy(struct smbdirect_socket *sc) @@ -544,6 +574,7 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) disable_work_sync(&sc->recv_io.posted.refill_work); disable_work_sync(&sc->idle.immediate_work); disable_delayed_work_sync(&sc->idle.timer_work); + disable_work_sync(&sc->listen.purge_orphaned_work); if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); @@ -607,6 +638,19 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) spin_lock_irqsave(&sc->listen.lock, flags); list_splice_tail_init(&sc->listen.ready, &pending_list); list_splice_tail_init(&sc->listen.pending, &pending_list); + /* + * purge_orphaned_work is already disabled above, + * so we also need to release the orphaned sockets. + * + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them and + * prevents them from moving themselves to + * the orphaned list via + * smbdirect_listen_orphan_socket(). + */ + list_splice_tail_init(&sc->listen.orphaned, &pending_list); + list_for_each_entry(psc, &pending_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); spin_unlock_irqrestore(&sc->listen.lock, flags); /* It's not possible for upper layer to get to reassembly */ @@ -650,7 +694,6 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) "release %zu pending sockets\n", psockets); list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) { list_del_init(&psc->accept.list); - psc->accept.listener = NULL; smbdirect_socket_release(psc); } if (sc->listen.backlog != -1) /* was a listener */ @@ -777,7 +820,17 @@ static void smbdirect_socket_release_destroy(struct kref *kref) * in DESTROYED state, before we free the memory. */ smbdirect_socket_destroy_sync(sc); - kfree(sc); + + /* + * Only a listener (backlog != -1) is ever dereferenced + * via sc->accept.listener under rcu_read_lock(), see + * smbdirect_listen_orphan_socket(). Other sockets can be + * freed immediately. + */ + if (sc->listen.backlog != -1) /* was a listener */ + kfree_rcu(sc, refs.rcu); + else + kfree(sc); } void smbdirect_socket_release(struct smbdirect_socket *sc) diff --git a/fs/smb/smbdirect/socket.h b/fs/smb/smbdirect/socket.h index c09eddd8ad16..14114a2567a2 100644 --- a/fs/smb/smbdirect/socket.h +++ b/fs/smb/smbdirect/socket.h @@ -151,6 +151,12 @@ struct smbdirect_socket { * the disconnect refcount. */ struct kref destroy; + + /* + * smbdirect_socket_release_destroy() uses + * kfree_rcu(), see accept.listener. + */ + struct rcu_head rcu; } refs; /* RDMA related */ @@ -216,6 +222,16 @@ struct smbdirect_socket { * only be > 0. */ int backlog; + /* + * Sockets on pending or ready that failed + * move themselves to orphaned and queue + * purge_orphaned_work, which releases them. + * So that they are freed while the listener + * is still alive. They still count against + * the backlog until they are released. + */ + struct list_head orphaned; + struct work_struct purge_orphaned_work; } listen; /* @@ -226,7 +242,30 @@ struct smbdirect_socket { * connection. */ struct { + /* + * This is only set, protected by + * listener->listen.lock, while the socket + * is owned by the listener (on its pending, + * ready or orphaned list). The one who clears + * it is responsible for releasing the socket. + * + * The memory of a struct smbdirect_socket is + * freed via kfree_rcu(), so the listener + * can be dereferenced under rcu_read_lock(), + * even if accept.listener is cleared and + * the listener is released concurrently. + */ struct smbdirect_socket *listener; + /* + * Linkage on one of the listener's + * listen.{pending,ready,orphaned} lists. + * + * accept.list and accept.listener are set + * together under listener->listen.lock before + * smbdirect_accept_connect_request() is called, + * so whenever the socket is on one of those lists + * accept.listener is that listener (never NULL). + */ struct list_head list; } accept; @@ -588,6 +627,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) spin_lock_init(&sc->listen.lock); INIT_LIST_HEAD(&sc->listen.pending); INIT_LIST_HEAD(&sc->listen.ready); + INIT_LIST_HEAD(&sc->listen.orphaned); + INIT_WORK(&sc->listen.purge_orphaned_work, __smbdirect_socket_disabled_work); + disable_work_sync(&sc->listen.purge_orphaned_work); sc->listen.backlog = -1; /* not a listener */ init_waitqueue_head(&sc->listen.wait_queue); -- 2.43.0 From metze at samba.org Tue Oct 6 19:08:57 2026 From: metze at samba.org (Stefan Metzmacher) Date: Tue, 6 Oct 2026 21:08:57 +0200 Subject: [PATCH v3 3/3] smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() In-Reply-To: References: Message-ID: A socket on the ready list is in SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, but it may fail before smbdirect_socket_accept() picks it up, e.g. RDMA_CM_EVENT_DISCONNECTED already moved it to SMBDIRECT_SOCKET_DISCONNECTED. smbdirect_socket_accept() unconditionally overwrote the status with SMBDIRECT_SOCKET_CONNECTED and handed out the already disconnected socket. smbdirect_socket_cleanup_work() then called rdma_disconnect() on it again, and before the recent commit smbdirect_socket_destroy_sync() waited forever for an RDMA_CM_EVENT_DISCONNECTED that already happened. Now we check first_error and change the status to SMBDIRECT_SOCKET_CONNECTED only via cmpxchg() from SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, while we still hold listen.lock. If the socket already failed, we move it to the orphaned list and queue purge_orphaned_work, like smbdirect_listen_orphan_socket() does, and try the next one. If we only found failed sockets, we wait for the next one. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 71 +++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 9353c7647735..1ef819511f6f 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -822,7 +822,11 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long if (ret < 0) return ret; - return 0; + /* + * Return the remaining timeout, so the caller can carry it + * over to the next smbdirect_socket_wait_for_accept() call. + */ + return ret; } struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, @@ -830,8 +834,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct proto_accept_arg *arg) { struct smbdirect_socket *nsc; + bool orphaned; unsigned long flags; +again: + orphaned = false; + if (lsc->status != SMBDIRECT_SOCKET_LISTENING) { arg->err = -EINVAL; return NULL; @@ -843,7 +851,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } if (list_empty_careful(&lsc->listen.ready)) { - int ret; + long ret; if (timeo == 0) { arg->err = -EAGAIN; @@ -851,16 +859,51 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } ret = smbdirect_socket_wait_for_accept(lsc, timeo); - if (ret) { + if (ret < 0) { arg->err = ret; return NULL; } + /* + * Carry the remaining timeout over, so that a stream of + * failed connections that we orphan and skip (goto again) + * can't reset the caller's timeout and wait forever. + */ + timeo = ret; } spin_lock_irqsave(&lsc->listen.lock, flags); - nsc = list_first_entry_or_null(&lsc->listen.ready, - struct smbdirect_socket, - accept.list); + while ((nsc = list_first_entry_or_null(&lsc->listen.ready, + struct smbdirect_socket, + accept.list))) { + /* + * nsc may have failed after it was moved + * to the ready list, e.g. RDMA_CM_EVENT_DISCONNECTED + * already moved it to SMBDIRECT_SOCKET_DISCONNECTED. + * We must not overwrite that with + * SMBDIRECT_SOCKET_CONNECTED and hand + * out an already disconnected socket. + * + * Doing this under listen.lock means nsc + * still belongs to us, so we can just move + * a failed socket to the orphaned list, + * like smbdirect_listen_orphan_socket() does, + * and try the next one. + */ + if (!READ_ONCE(nsc->first_error) && + cmpxchg(&nsc->status, + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, + SMBDIRECT_SOCKET_CONNECTED) == + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) + break; + + smbdirect_log_rdma_event(nsc, SMBDIRECT_LOG_INFO, + "orphaning failed socket status=%s first_error=%1pe\n", + smbdirect_socket_status_string(nsc->status), + SMBDIRECT_DEBUG_ERR_PTR(nsc->first_error)); + list_move_tail(&nsc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + orphaned = true; + } if (nsc) { WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); @@ -868,6 +911,12 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } spin_unlock_irqrestore(&lsc->listen.lock, flags); if (!nsc) { + /* + * If we only found failed sockets, + * we wait for the next one. + */ + if (orphaned) + goto again; arg->err = -EAGAIN; return NULL; } @@ -878,12 +927,14 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, * so it didn't grant any credits to us. * * The caller expects a connected socket - * now as there are no credits anyway. + * now as there are no credits anyway, + * above we already changed to SMBDIRECT_SOCKET_CONNECTED + * under the lsc->listen.lock and with cmpxchg. * - * Then we send the negotiation response in - * order to grant credits to the peer. + * Now we send the negotiation response in + * order to grant credits to the peer, + * as the socket is now visible to the application layer. */ - nsc->status = SMBDIRECT_SOCKET_CONNECTED; smbdirect_accept_negotiate_finish(nsc, 0); return nsc; -- 2.43.0 From pc at manguebit.org Tue Oct 6 20:13:19 2026 From: pc at manguebit.org (Paulo Alcantara) Date: Tue, 06 Oct 2026 17:13:19 -0300 Subject: [PATCH 0/5] smb: client: don't use server->smbd_conn without a reference or lock In-Reply-To: <1111a3da-3eb7-44e0-8c68-4a09bb4615a1@samba.org> References: <1111a3da-3eb7-44e0-8c68-4a09bb4615a1@samba.org> Message-ID: Stefan Metzmacher writes: > I'll address these https://sashiko.dev/#/patchset/cover.1791225453.git.metze%40samba.org > and resubmit v2 later... Sounds good. Thanks for the fixes. Looking forward to seeing the test results from Tom. From linkinjeon at kernel.org Thu Oct 8 02:26:20 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Thu, 8 Oct 2026 11:26:20 +0900 Subject: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: > ret = smbdirect_accept_connect_request(nsc, &event->param.conn); > - if (ret) > - goto accept_connect_failed; > + if (ret) { > + /* > + * The rdma_cm core holds both the listener's and nsc's > + * id_priv->handler_mutex across this CONNECT_REQUEST handler, > + * so neither can go away under us here and nsc cannot receive > + * any rdma event: it is safe to hand nsc to its teardown. > + * > + * That teardown must be deferred though: nsc->rdma.cm_id > + * (= new_id) has its handler_mutex held by us, so it cannot be > + * destroyed synchronously from here. > + * smbdirect_socket_schedule_cleanup() only queues work; > + * smbdirect_socket_cleanup_work() then orphans nsc off the > + * listener and smbdirect_listen_purge_orphaned_work() releases > + * it, destroying nsc's cm_id with rdma_destroy_id(). > + */ > + smbdirect_socket_schedule_cleanup(nsc, ret); Does this make return 0 unsafe? This path now queues cleanup after creating the QP, then returns before cleanup runs. If DEVICE_REMOVAL event arrives in that gap, the temporary handler sets sc->rdma.cm_id to NULL and returns -ESTALE. The CM core then destroys the ID, and the cleanup worker tries to destroy the QP with NULL ID, causing kernel oops ? > + } > > + /* > + * Always return 0 so the rdma_cm core keeps new_id: nsc owns it now. > + * On success it stays connected; on failure its deferred teardown > + * above destroys it. > + */ > return 0; > > -accept_connect_failed: > - spin_lock_irqsave(&lsc->listen.lock, flags); > - list_del_init(&nsc->accept.list); > - nsc->accept.listener = NULL; > - spin_unlock_irqrestore(&lsc->listen.lock, flags); > set_settings_failed: > set_params_failed: > /* From metze at samba.org Thu Oct 8 06:10:30 2026 From: metze at samba.org (Stefan Metzmacher) Date: Thu, 8 Oct 2026 08:10:30 +0200 Subject: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: <76ad9700-db36-4c9e-966a-3dd403706780@samba.org> Am 08.10.26 um 06:53 schrieb ???: > Re: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a > listener > > Hi Namjae, Metze, > > On Thu, Oct 8, 2026, Namjae Jeon wrote: >> Does this make return 0 unsafe? This path now queues cleanup after >> creating the QP, then returns before cleanup runs. If DEVICE_REMOVAL >> event arrives in that gap, the temporary handler sets sc->rdma.cm_id >> to NULL and returns -ESTALE. The CM core then destroys the ID, and the >> cleanup worker tries to destroy the QP with NULL ID, causing kernel >> oops ? Thanks Namjae for finding this! > I agree, and I think there is also a UAF variant of the same window. > > The patch comment says nsc "cannot receive any rdma event", but that > is only true while the CONNECT_REQUEST handler runs. After return 0 the > core drops new_id's handler_mutex, so cma_send_device_removal_put() can > deliver DEVICE_REMOVAL before smbdirect_socket_cleanup_work() runs. > > In smbdirect_socket_destroy(): > > if (sc->rdma.cm_id) > rdma_lock_handler(sc->rdma.cm_id); > > sc->rdma.cm_id is read without a lock. If the worker reads a non-NULL > pointer, and then the removal handler clears it and returns -ESTALE, > destroy_id_handler_unlock() frees the id_priv and rdma_lock_handler() > touches freed memory. So it is UAF, not only a NULL deref. > > Also, the QP/CQ/mem pools are still alive at device removal time. They > need to be released before the removal handler returns, otherwise the > later teardown runs against a device that is already gone. > > The old code had no such window, because the failure path returned > non-zero and the core destroyed the id while handler_mutex was held. > > Possible direction: > - in the DEVICE_REMOVAL branch, destroy QP/CQ/MRs/PD synchronously > (rdma_destroy_qp() is fine under handler_mutex), clear cm_id under > the lock and return non-zero; > - in the teardown, read/clear sc->rdma.cm_id under a lock and skip the > RDMA teardown when it is NULL. > > I will try to reproduce it with rxe + "rdma link delete" under KASAN, > forcing a failure in smbdirect_accept_connect_request() and delaying > smbdirect_socket_cleanup_work(), and report back. I think while we have smbdirect_new_rdma_event_handler it's fine. But starting with smbdirect_socket_init_accepting which currently sets it so smbdirect_socket_rdma_event_handler, still fine until we call smbdirect_accept_connect_request, I think before calling smbdirect_accept_connect_request() we need to set smbdirect_accept_rdma_event_handler at the beginning. Do you agree that would fix it? Thanks! metze From linkinjeon at kernel.org Thu Oct 8 06:19:40 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Thu, 8 Oct 2026 15:19:40 +0900 Subject: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: <76ad9700-db36-4c9e-966a-3dd403706780@samba.org> References: <76ad9700-db36-4c9e-966a-3dd403706780@samba.org> Message-ID: On Thu, Oct 8, 2026 at 3:10?PM Stefan Metzmacher wrote: > > Am 08.10.26 um 06:53 schrieb ???: > > Re: [PATCH v3 2/3] smb: smbdirect: release failed pending sockets of a > > listener > > > > Hi Namjae, Metze, > > > > On Thu, Oct 8, 2026, Namjae Jeon wrote: > >> Does this make return 0 unsafe? This path now queues cleanup after > >> creating the QP, then returns before cleanup runs. If DEVICE_REMOVAL > >> event arrives in that gap, the temporary handler sets sc->rdma.cm_id > >> to NULL and returns -ESTALE. The CM core then destroys the ID, and the > >> cleanup worker tries to destroy the QP with NULL ID, causing kernel > >> oops ? > > Thanks Namjae for finding this! > > > I agree, and I think there is also a UAF variant of the same window. > > > > The patch comment says nsc "cannot receive any rdma event", but that > > is only true while the CONNECT_REQUEST handler runs. After return 0 the > > core drops new_id's handler_mutex, so cma_send_device_removal_put() can > > deliver DEVICE_REMOVAL before smbdirect_socket_cleanup_work() runs. > > > > In smbdirect_socket_destroy(): > > > > if (sc->rdma.cm_id) > > rdma_lock_handler(sc->rdma.cm_id); > > > > sc->rdma.cm_id is read without a lock. If the worker reads a non-NULL > > pointer, and then the removal handler clears it and returns -ESTALE, > > destroy_id_handler_unlock() frees the id_priv and rdma_lock_handler() > > touches freed memory. So it is UAF, not only a NULL deref. > > > > Also, the QP/CQ/mem pools are still alive at device removal time. They > > need to be released before the removal handler returns, otherwise the > > later teardown runs against a device that is already gone. > > > > The old code had no such window, because the failure path returned > > non-zero and the core destroyed the id while handler_mutex was held. > > > > Possible direction: > > - in the DEVICE_REMOVAL branch, destroy QP/CQ/MRs/PD synchronously > > (rdma_destroy_qp() is fine under handler_mutex), clear cm_id under > > the lock and return non-zero; > > - in the teardown, read/clear sc->rdma.cm_id under a lock and skip the > > RDMA teardown when it is NULL. > > > > I will try to reproduce it with rxe + "rdma link delete" under KASAN, > > forcing a failure in smbdirect_accept_connect_request() and delaying > > smbdirect_socket_cleanup_work(), and report back. > > I think while we have smbdirect_new_rdma_event_handler it's fine. > But starting with smbdirect_socket_init_accepting which currently sets > it so smbdirect_socket_rdma_event_handler, still fine until > we call smbdirect_accept_connect_request, I think before calling > smbdirect_accept_connect_request() we need to set smbdirect_accept_rdma_event_handler > at the beginning. > > Do you agree that would fix it? Agreed, that should fix it. Thanks! From metze at samba.org Fri Oct 9 10:02:10 2026 From: metze at samba.org (Stefan Metzmacher) Date: Fri, 9 Oct 2026 12:02:10 +0200 Subject: [PATCH v4 0/3] smb: smbdirect: fix listener backlog leak and teardown races Message-ID: Hi Namjae, These fix a set of problems in the shared smbdirect module (fs/smb/smbdirect/), reached through the listener/accept path, which in practice is driven by ksmbd for incoming SMB Direct connections. The main one is a pre-authentication remote denial of service: the listener keeps every accepted connection on a fixed-size backlog (listen.pending, limit 10 for ksmbd). A connection is only removed from that backlog on the success path (promotion to listen.ready after a completed negotiate, or dequeue by accept()). A connection that is accepted at the RDMA level but then fails before it is accepted by the upper layer - negotiate timeout, peer disconnect, or an invalid negotiate request - is never removed, so its backlog slot is leaked. After about ten such events the listener rejects every new connection with -EBUSY and SMB Direct stays unusable until the service is restarted. An unauthenticated peer can trigger this with ~10 aborted connections. This was reported by Lee Seong Hyeon : https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ The series: - smbdirect_socket_destroy_sync() no longer waits for RDMA_CM_EVENT_DISCONNECTED after rdma_disconnect(). That wait could take very long (until the rdma cm gives up, e.g. a peer that just vanished) or never complete if the event already happened and the status was overwritten. rdma_destroy_id() in smbdirect_socket_destroy() is enough to stop further events; the rest of the disconnect protocol is handled by the rdma core asynchronously. This also makes releasing orphaned sockets (next patch) non-blocking. - A failed, not-yet-accepted child of a listener now moves itself to a new listen.orphaned list at the end of its cleanup work and queues listen.purge_orphaned_work, which releases it, so it no longer leaks its backlog slot (nor its struct sock / RDMA resources) for the lifetime of the listener. sc->accept.listener is only changed under listener->listen.lock, and whoever clears it owns the release; the listener is freed via kfree_rcu() so a child can dereference it under rcu_read_lock(). This is the DoS fix. - smbdirect_socket_accept() no longer hands out a socket that failed after it was put on the ready list (e.g. the peer disconnected in the meantime); it checks first_error / the status under listen.lock, orphans such a socket and tries the next one. And it also no longer returns -EAGAIN if a non zero timeout was passed. I tested the reproducer and the problem is fixed and I run various xfstests. I think these are important and should go into 7.3 Changes since v3: (https://lore.kernel.org/r/cover.1791311817.git.metze at samba.org/) - "smb: smbdirect: release failed pending sockets of a listener": install the real event handler and the expected event at the very start of smbdirect_accept_connect_request(), before anything that can fail. On an early error the caller only schedules nsc's deferred cleanup and returns 0, so the rdma_cm core keeps the cm_id - it must then already carry smbdirect_accept_rdma_event_handler(), which turns any unexpected event (e.g. RDMA_CM_EVENT_DEVICE_REMOVAL) into a clean smbdirect_socket_schedule_cleanup(). Previously the placeholder smbdirect_socket_rdma_event_handler() left by smbdirect_socket_init_accepting() stayed installed on that path and returned -ESTALE, making the rdma_cm core destroy the cm_id behind the back of the pending teardown. - smbdirect_socket_accept always goes back to smbdirect_socket_wait_for_accept when no valid new socket was found, so that we don't return -EAGAIN unless a zero timeout was given. Changes since v2: (https://lore.kernel.org/r/cover.1791229120.git.metze at samba.org/) - "smb: smbdirect: release failed pending sockets of a listener": reworked the connect-request error handling after more Sashiko review. smbdirect_accept_connect_request() no longer tears down any RDMA state or clears the cm_id on failure; it only returns a not-yet-posted recv_io to the pool and returns the error. smbdirect_listen_connect_request() now sets nsc->accept.listener and publishes nsc on the pending list *before* it calls smbdirect_accept_connect_request(), and on a non-zero return it schedules nsc's teardown with smbdirect_socket_schedule_cleanup() and always returns 0, so the rdma_cm core keeps the connection id and nsc owns it (and destroys it in its own deferred teardown). The teardown must be deferred because nsc's id_priv->handler_mutex is held by the rdma_cm core across the CONNECT_REQUEST handler; that same lock (and the listener's) also guarantees nsc can't go away under us. This closes the race Sashiko reported, where a connection that established between rdma_accept() and setting the listener could be left stranded on the pending list. Changes since v1: (https://lore.kernel.org/r/cover.1791224972.git.metze at samba.org) - "smb: smbdirect: release failed pending sockets of a listener": fix a use-after-free and a missed-orphan race in smbdirect_listen_connect_request(), spotted by Sashiko. The nsc->first_error check and the orphaning are now done under listen.lock, together with setting nsc->accept.listener, so a concurrent smbdirect_socket_cleanup_work()/purge_orphaned_work() can neither free nsc while we still look at it, nor miss the orphaning and leak it on the pending list. - "smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept()": bound the accept() wait across the "skip a failed socket and try the next one" retries, also spotted by Sashiko. smbdirect_socket_wait_for_accept() now returns the remaining timeout and smbdirect_socket_accept() carries it over, so a stream of failed connections can no longer reset the caller's timeout. ksmbd passes MAX_SCHEDULE_TIMEOUT, so it is unaffected in practice. - Added Reported-by:/Closes: for the Sashiko reviews. Stefan Metzmacher (3): smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() smb: smbdirect: release failed pending sockets of a listener smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() fs/smb/smbdirect/accept.c | 225 ++++++++++++++++++++++++---------- fs/smb/smbdirect/connection.c | 12 +- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 137 +++++++++++++++++++-- fs/smb/smbdirect/socket.c | 113 +++++++++++++++-- fs/smb/smbdirect/socket.h | 42 +++++++ 6 files changed, 438 insertions(+), 93 deletions(-) -- 2.43.0 From metze at samba.org Fri Oct 9 10:02:11 2026 From: metze at samba.org (Stefan Metzmacher) Date: Fri, 9 Oct 2026 12:02:11 +0200 Subject: [PATCH v4 1/3] smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in smbdirect_socket_destroy_sync() In-Reply-To: References: Message-ID: <22ab29ca3288e96cf92d4991c32b8975297d9a28.1791540051.git.metze@samba.org> smbdirect_socket_destroy_sync() waited for RDMA_CM_EVENT_DISCONNECTED after smbdirect_socket_cleanup_work() called rdma_disconnect(). That can take very long, e.g. if the peer just disappeared, until the rdma cm gives up, or forever if the event already happened, but the status was overwritten afterwards. This is not needed: smbdirect_socket_destroy() drains the qp under rdma_lock_handler(), destroys it and calls rdma_destroy_id(), which only waits for a currently running event handler and makes sure no further events are delivered. The rest of the disconnect protocol (DREQ/DREP and timewait for IB, abrupt close for iWarp) is handled by the rdma core asynchronously. drivers/nvme/host/rdma.c also just calls rdma_disconnect() and ib_drain_qp() before rdma_destroy_id(). So smbdirect_socket_destroy() now also accepts SMBDIRECT_SOCKET_DISCONNECTING and changes the status to SMBDIRECT_SOCKET_DISCONNECTED itself under rdma_lock_handler(). We could still get RDMA_CM_EVENT_DISCONNECTED in the small windows between rdma_unlock_handler() and rdma_destroy_id(), but in that case smbdirect_connection_rdma_event_handler() is basically a no-op. Fixes: 422a2436697d ("smb: smbdirect: introduce smbdirect_socket_destroy[_sync]()") Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Cc: linux-rdma at vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/connection.c | 12 ++++---- fs/smb/smbdirect/socket.c | 56 ++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index afd31fa12a36..f48c857bda5a 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -83,9 +83,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup[_status]() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. */ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL) smbdirect_socket_schedule_cleanup_status(sc, @@ -113,9 +113,9 @@ static int smbdirect_connection_rdma_event_handler(struct rdma_cm_id *id, * smbdirect_socket_schedule_cleanup_status() => * smbdirect_socket_cleanup_work(). * - * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING, - * but never ever get RDMA_CM_EVENT_DISCONNECTED and - * never reach SMBDIRECT_SOCKET_DISCONNECTED. + * As otherwise we'd set SMBDIRECT_SOCKET_DISCONNECTING + * and call rdma_disconnect(), but never ever get + * RDMA_CM_EVENT_DISCONNECTED. * * This is also a normal disconnect so * SMBDIRECT_LOG_INFO should be good enough diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index bb02df6158b9..c36cb7cc0088 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -511,7 +511,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->status == SMBDIRECT_SOCKET_DESTROYED) return; - WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED, + /* + * smbdirect_socket_destroy_sync() doesn't wait + * for RDMA_CM_EVENT_DISCONNECTED, so we may still + * be in SMBDIRECT_SOCKET_DISCONNECTING + * (or already reached SMBDIRECT_SOCKET_DISCONNECTED) + */ + WARN_ONCE(sc->status < SMBDIRECT_SOCKET_DISCONNECTING, "status=%s first_error=%1pe", smbdirect_socket_status_string(sc->status), SMBDIRECT_DEBUG_ERR_PTR(sc->first_error)); @@ -542,6 +548,32 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); + /* + * We hold the handler lock, so the rdma event + * handlers can't change the status anymore. + * + * If RDMA_CM_EVENT_DISCONNECTED didn't arrive yet, + * we just stop waiting for it here. + * + * We already disabled disconnect_work above + * and before we call rdma_unlock_handler below + * we call smbdirect_connection_destroy_qp which + * sets sc->ib.qp = NULL. + * + * Between rdma_unlock_handler() and + * rdma_destroy_id() below there's a small + * windows where RDMA_CM_EVENT_DISCONNECTED + * could still arrive. + * + * But smbdirect_connection_rdma_event_handler + * will be a noop when calling smbdirect_socket_schedule_cleanup* + * ib_drain_qp() also won't be called. + */ + if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { + sc->status = SMBDIRECT_SOCKET_DISCONNECTED; + smbdirect_socket_wake_up_all(sc); + } + if (sc->ib.qp) { smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, "drain qp\n"); @@ -679,17 +711,21 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc) "destroying rdma session\n"); if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING) smbdirect_socket_cleanup_work(&sc->disconnect_work); - if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) { - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "wait for transport being disconnected\n"); - wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED); - smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, - "waited for transport being disconnected\n"); - } /* - * Once we reached SMBDIRECT_SOCKET_DISCONNECTED, - * we should call smbdirect_socket_destroy() + * We don't wait for RDMA_CM_EVENT_DISCONNECTED, + * rdma_disconnect() was already called by + * smbdirect_socket_cleanup_work() if needed + * and smbdirect_socket_destroy() drains the qp, + * destroys it and calls rdma_destroy_id(), which + * only waits for a currently running event handler + * and makes sure no further events are delivered. + * The rest of the disconnect protocol is handled + * by the rdma core asynchronously. + * + * Waiting for RDMA_CM_EVENT_DISCONNECTED could + * take very long or forever, e.g. if the peer + * just disappeared. */ smbdirect_socket_destroy(sc); smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, -- 2.43.0 From metze at samba.org Fri Oct 9 10:02:12 2026 From: metze at samba.org (Stefan Metzmacher) Date: Fri, 9 Oct 2026 12:02:12 +0200 Subject: [PATCH v4 2/3] smb: smbdirect: release failed pending sockets of a listener In-Reply-To: References: Message-ID: <4cad9c1248304b0981fc57342a7580f19712c99c.1791540051.git.metze@samba.org> A child of a listener only left the listen.pending or listen.ready list via smbdirect_socket_accept() or when the listener was destroyed. So every connection that failed before it was accepted (negotiate timeout, peer disconnect, invalid negotiate request, ...) stayed on the list and counted against the backlog until the listener was destroyed. After about backlog (10 for ksmbd) such connections smbdirect_listen_connect_request() rejected every new connection with -EBUSY, so an unauthenticated peer was able to permanently stop ksmbd from accepting SMB-Direct connections. Now a failed child moves itself to a new listen.orphaned list at the end of smbdirect_socket_cleanup_work(), after the disconnect was started, and queues listen.purge_orphaned_work, which releases it with smbdirect_socket_release(), without holding the listener's rdma_lock_handler() lock, in the same way smbdirect_socket_destroy() does for the remaining children. Orphaned sockets still count against the backlog until they are released. sc->accept.listener is now only changed under listener->listen.lock and whoever clears it is responsible for releasing the socket: smbdirect_socket_accept(), purge_orphaned_work or the destroy of the listener. In order to make that work: - a struct smbdirect_socket that is a listener is now freed via kfree_rcu(), so that a child can dereference sc->accept.listener under rcu_read_lock(), even if the listener is released concurrently. - smbdirect_listen_connect_request() adds nsc to the listen.pending list and sets nsc->accept.listener before it calls smbdirect_accept_connect_request(), and always returns 0 so the rdma_cm core keeps the connection id and nsc owns it. On a non-zero return it schedules nsc's teardown with smbdirect_socket_schedule_cleanup(), which orphans it off the listener so that purge_orphaned_work releases it; nsc destroys its own connection id with rdma_destroy_id() in that teardown. The teardown has to be deferred because the rdma_cm core holds nsc's id_priv->handler_mutex across the handler, so nsc->rdma.cm_id must not be destroyed synchronously from here. That same handler_mutex (and the listener's) also guarantees nsc can't go away under us, so setting the listener before the call is safe and lets a failure orphan nsc. - smbdirect_accept_negotiate_recv_work() no longer moves a socket to the ready list if it already failed or no longer belongs to the listener. Every accepting socket has a listener, so if sc->accept.listener is already cleared, it no longer sends a negotiate response, as the socket is about to be released. - smbdirect_socket_destroy() of the listener disables purge_orphaned_work and also releases the orphaned sockets. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: Lee Seong Hyeon Closes: https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Closes: https://sashiko.dev/#/patchset/cover.1791229120.git.metze%40samba.org Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 141 +++++++++++++++++++++++------------- fs/smb/smbdirect/internal.h | 2 + fs/smb/smbdirect/listen.c | 137 ++++++++++++++++++++++++++++++++--- fs/smb/smbdirect/socket.c | 57 ++++++++++++++- fs/smb/smbdirect/socket.h | 42 +++++++++++ 5 files changed, 314 insertions(+), 65 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 1b30ca8476c1..977144442974 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -19,15 +19,33 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, const struct rdma_conn_param *param) { struct smbdirect_socket_parameters *sp = &sc->parameters; - struct smbdirect_recv_io *recv_io; + struct smbdirect_recv_io *recv_io = NULL; u8 peer_initiator_depth; u8 peer_responder_resources; struct rdma_conn_param conn_param; __be32 ird_ord_hdr[2]; int ret; - if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED)) - return -EINVAL; + /* + * Install the real event handler (and the event we expect next) + * before anything that can fail. On an early error the caller + * (smbdirect_listen_connect_request()) only schedules our deferred + * cleanup and returns 0 to the rdma_cm core, which keeps the cm_id, so + * the cm_id must already carry smbdirect_accept_rdma_event_handler(): + * it turns any unexpected event (e.g. RDMA_CM_EVENT_DEVICE_REMOVAL) + * into a clean smbdirect_socket_schedule_cleanup(). Otherwise the + * placeholder smbdirect_socket_rdma_event_handler() installed by + * smbdirect_socket_init_accepting() stays in place and returns -ESTALE, + * which makes the rdma_cm core destroy the cm_id behind the back of our + * pending teardown. + */ + sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED; + sc->rdma.cm_id->event_handler = smbdirect_accept_rdma_event_handler; + + if (SMBDIRECT_CHECK_STATUS_WARN(sc, SMBDIRECT_SOCKET_CREATED)) { + ret = -EINVAL; + goto cleanup; + } /* * First set what the we as server are able to support @@ -48,7 +66,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_accept_init_params() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto init_params_failed; + goto cleanup; } ret = smbdirect_connection_create_qp(sc); @@ -56,7 +74,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_create_qp() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto create_qp_failed; + goto cleanup; } ret = smbdirect_connection_create_mem_pools(sc); @@ -64,7 +82,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_create_mem_pools() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto create_mem_failed; + goto cleanup; } recv_io = smbdirect_connection_get_recv_io(sc); @@ -73,7 +91,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_get_recv_io() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto get_recv_io_failed; + goto cleanup; } recv_io->cqe.done = smbdirect_accept_negotiate_recv_done; @@ -87,7 +105,7 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "smbdirect_connection_post_recv_io() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto post_recv_io_failed; + goto cleanup; } /* * From here recv_io is known to the RDMA QP and needs ib_drain_qp and @@ -123,14 +141,12 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, /* explicitly set above */ WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED); sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING; - sc->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED; - sc->rdma.cm_id->event_handler = smbdirect_accept_rdma_event_handler; ret = rdma_accept(sc->rdma.cm_id, &conn_param); if (ret) { smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, "rdma_accept() failed %1pe\n", SMBDIRECT_DEBUG_ERR_PTR(ret)); - goto rdma_accept_failed; + goto cleanup; } /* @@ -144,31 +160,25 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc, return 0; -rdma_accept_failed: +cleanup: /* - * The recv_io posted above is now owned by the QP (recv_io was set to - * NULL after a successful post). smbdirect_connection_destroy_qp() - * calls ib_drain_qp(), whose completion - * (smbdirect_accept_negotiate_recv_done) returns the recv_io to the - * free list via smbdirect_connection_put_recv_io(). It therefore MUST - * run BEFORE smbdirect_connection_destroy_mem_pools(): otherwise the - * posted recv_io is still outstanding when kmem_cache_destroy() runs - * ("Slab cache still has objects") and is later freed into an - * already-destroyed mempool (mempool_free_bulk NULL-ptr-deref). + * No RDMA state is torn down here, and in particular sc is not + * released: the caller (smbdirect_listen_connect_request()) owns that + * decision and schedules sc's teardown via + * smbdirect_socket_schedule_cleanup() on a non-zero return. All of the + * RDMA state - the QP, the memory pools and the cm_id itself - is then + * torn down asynchronously by smbdirect_socket_cleanup_work() -> + * smbdirect_socket_release() -> smbdirect_socket_destroy(), which + * drains the QP (returning any posted recv_io to the pool) before + * destroying the memory pools and destroys the cm_id with + * rdma_destroy_id(), all in the correct order. + * + * The only resource not reachable by that teardown is a recv_io that + * was taken from the pool but not yet posted to the QP (recv_io is set + * to NULL after a successful post), so hand it back here. */ - smbdirect_connection_destroy_qp(sc); - smbdirect_connection_destroy_mem_pools(sc); - return ret; -post_recv_io_failed: - /* post failed: recv_io was not accepted by the QP, still in hand */ if (recv_io) smbdirect_connection_put_recv_io(recv_io); -get_recv_io_failed: - smbdirect_connection_destroy_mem_pools(sc); -create_mem_failed: - smbdirect_connection_destroy_qp(sc); -create_qp_failed: -init_params_failed: return ret; } @@ -302,6 +312,7 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) struct smbdirect_socket *sc = container_of(work, struct smbdirect_socket, connect.work); struct smbdirect_socket_parameters *sp = &sc->parameters; + struct smbdirect_socket *lsc; struct smbdirect_recv_io *recv_io; struct smbdirect_negotiate_req *nreq; unsigned long flags; @@ -464,29 +475,55 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work) */ sp->max_fragmented_send_size = max_fragmented_size; - if (sc->accept.listener) { - struct smbdirect_socket *lsc = sc->accept.listener; - unsigned long flags; + /* + * Every accepting socket was created by + * smbdirect_listen_connect_request() and has a + * listener. If it's already cleared, the listener + * (or its purge_orphaned_work) is responsible for + * releasing us, so we must not send a negotiate + * response. + * + * The memory of the listener is freed via kfree_rcu(), + * see smbdirect_listen_orphan_socket(). + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del(&sc->accept.list); - list_add_tail(&sc->accept.list, &lsc->listen.ready); + spin_lock_irqsave(&lsc->listen.lock, flags); + /* + * The listener (or its purge_orphaned_work) + * may have cleared sc->accept.listener in the + * meantime and is responsible for releasing us. + * + * If we already failed, we're either + * already on the orphaned list or + * smbdirect_socket_cleanup_work() will + * move us there. + * + * In both cases we must not move us + * to the ready list. + */ + if (sc->accept.listener == lsc && !READ_ONCE(sc->first_error)) { + list_move_tail(&sc->accept.list, &lsc->listen.ready); wake_up(&lsc->listen.wait_queue); - spin_unlock_irqrestore(&lsc->listen.lock, flags); - - /* - * smbdirect_socket_accept() will call - * smbdirect_accept_negotiate_finish(nsc, 0); - * - * So that we don't send the negotiation - * response that grants credits to the peer - * before the socket is accepted by the - * application. - */ - return; } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); - ntstatus = le32_to_cpu(STATUS_SUCCESS); + /* + * smbdirect_socket_accept() will call + * smbdirect_accept_negotiate_finish(nsc, 0); + * + * So that we don't send the negotiation + * response that grants credits to the peer + * before the socket is accepted by the + * application. + */ + return; not_supported: smbdirect_accept_negotiate_finish(sc, ntstatus); @@ -839,7 +876,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct smbdirect_socket, accept.list); if (nsc) { - nsc->accept.listener = NULL; + WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); arg->is_empty = list_empty_careful(&lsc->listen.ready); } diff --git a/fs/smb/smbdirect/internal.h b/fs/smb/smbdirect/internal.h index e9959e6dc13a..69d0c9f899de 100644 --- a/fs/smb/smbdirect/internal.h +++ b/fs/smb/smbdirect/internal.h @@ -74,6 +74,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc); +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc); + int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc, enum smbdirect_socket_status expected_status, int unexpected_errno, diff --git a/fs/smb/smbdirect/listen.c b/fs/smb/smbdirect/listen.c index 2f78bcaedbf8..3c91f00d95ce 100644 --- a/fs/smb/smbdirect/listen.c +++ b/fs/smb/smbdirect/listen.c @@ -10,6 +10,78 @@ static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event); +/* + * This is called by a socket that failed while it + * is still on the pending or ready list of its + * listener, typically at the end of + * smbdirect_socket_cleanup_work(), when the + * disconnect was already started. + * + * It moves itself to the orphaned list and + * lets smbdirect_listen_purge_orphaned_work() + * release it. Otherwise it would stay on the + * pending or ready list until the listener is + * destroyed and fill up the backlog, so that + * no new connections would be accepted. + * + * It's fine to call this more than once. + */ +void smbdirect_listen_orphan_socket(struct smbdirect_socket *sc) +{ + struct smbdirect_socket *lsc; + unsigned long flags; + + /* + * The memory of the listener is freed via + * kfree_rcu(), so it's safe to dereference it + * under rcu_read_lock(), even if sc->accept.listener + * is cleared and the listener is released concurrently. + */ + rcu_read_lock(); + lsc = READ_ONCE(sc->accept.listener); + if (!lsc) { + rcu_read_unlock(); + return; + } + + spin_lock_irqsave(&lsc->listen.lock, flags); + if (sc->accept.listener == lsc) { + list_move_tail(&sc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } + spin_unlock_irqrestore(&lsc->listen.lock, flags); + rcu_read_unlock(); +} + +static void smbdirect_listen_purge_orphaned_work(struct work_struct *work) +{ + struct smbdirect_socket *lsc = + container_of(work, struct smbdirect_socket, listen.purge_orphaned_work); + struct smbdirect_socket *psc, *tsc; + LIST_HEAD(orphaned_list); + unsigned long flags; + + /* + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them. + */ + spin_lock_irqsave(&lsc->listen.lock, flags); + list_splice_tail_init(&lsc->listen.orphaned, &orphaned_list); + list_for_each_entry(psc, &orphaned_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); + spin_unlock_irqrestore(&lsc->listen.lock, flags); + + /* + * We don't hold the listener's rdma_lock_handler() + * lock here, see smbdirect_socket_destroy() + * for why that's important. + */ + list_for_each_entry_safe(psc, tsc, &orphaned_list, accept.list) { + list_del_init(&psc->accept.list); + smbdirect_socket_release(psc); + } +} + int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) { int ret; @@ -47,6 +119,9 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog) sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler; rdma_unlock_handler(sc->rdma.cm_id); + INIT_WORK(&sc->listen.purge_orphaned_work, + smbdirect_listen_purge_orphaned_work); + ret = rdma_listen(sc->rdma.cm_id, backlog); if (ret) { sc->first_error = ret; @@ -214,6 +289,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, size_t backlog = max_t(size_t, 1, lsc->listen.backlog); size_t psockets; size_t rsockets; + size_t osockets; int ret; if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) { @@ -254,14 +330,21 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, spin_lock_irqsave(&lsc->listen.lock, flags); psockets = list_count_nodes(&lsc->listen.pending); rsockets = list_count_nodes(&lsc->listen.ready); + /* + * Orphaned sockets still count against + * the backlog until they are released by + * smbdirect_listen_purge_orphaned_work(). + */ + osockets = list_count_nodes(&lsc->listen.orphaned); spin_unlock_irqrestore(&lsc->listen.lock, flags); if (psockets > backlog || rsockets > backlog || - (psockets + rsockets) > backlog) { + osockets > backlog || + (psockets + rsockets + osockets) > backlog) { smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR, - "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n", - lsc->listen.backlog, backlog, psockets, rsockets); + "Backlog[%d][%zu] full pending[%zu] ready[%zu] orphaned[%zu]\n", + lsc->listen.backlog, backlog, psockets, rsockets, osockets); return -EBUSY; } @@ -279,22 +362,54 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc, if (ret) goto set_settings_failed; + /* + * Publish nsc on the pending list with accept.listener set before + * smbdirect_accept_connect_request() calls rdma_accept(). Once the + * connection can establish, smbdirect_accept_negotiate_recv_work() + * may run and it must observe accept.listener, otherwise nsc would + * be left stranded on the pending list forever. + * + * The listener's handler_mutex is held while we're called, so + * smbdirect_socket_destroy() of the listener can't reach nsc on the + * pending list before we're done. + * + * From here nsc is published on the pending list: on failure of + * smbdirect_accept_connect_request() below we schedule nsc's teardown, + * which orphans nsc off the listener so + * smbdirect_listen_purge_orphaned_work() -> smbdirect_socket_release() + * releases it. + */ spin_lock_irqsave(&lsc->listen.lock, flags); list_add_tail(&nsc->accept.list, &lsc->listen.pending); - nsc->accept.listener = lsc; + WRITE_ONCE(nsc->accept.listener, lsc); spin_unlock_irqrestore(&lsc->listen.lock, flags); ret = smbdirect_accept_connect_request(nsc, &event->param.conn); - if (ret) - goto accept_connect_failed; + if (ret) { + /* + * The rdma_cm core holds both the listener's and nsc's + * id_priv->handler_mutex across this CONNECT_REQUEST handler, + * so neither can go away under us here and nsc cannot receive + * any rdma event: it is safe to hand nsc to its teardown. + * + * That teardown must be deferred though: nsc->rdma.cm_id + * (= new_id) has its handler_mutex held by us, so it cannot be + * destroyed synchronously from here. + * smbdirect_socket_schedule_cleanup() only queues work; + * smbdirect_socket_cleanup_work() then orphans nsc off the + * listener and smbdirect_listen_purge_orphaned_work() releases + * it, destroying nsc's cm_id with rdma_destroy_id(). + */ + smbdirect_socket_schedule_cleanup(nsc, ret); + } + /* + * Always return 0 so the rdma_cm core keeps new_id: nsc owns it now. + * On success it stays connected; on failure its deferred teardown + * above destroys it. + */ return 0; -accept_connect_failed: - spin_lock_irqsave(&lsc->listen.lock, flags); - list_del_init(&nsc->accept.list); - nsc->accept.listener = NULL; - spin_unlock_irqrestore(&lsc->listen.lock, flags); set_settings_failed: set_params_failed: /* diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c index c36cb7cc0088..e270e36b3145 100644 --- a/fs/smb/smbdirect/socket.c +++ b/fs/smb/smbdirect/socket.c @@ -319,6 +319,15 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc, * (smbdirect_socket_destroy) to reap. */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -427,6 +436,15 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * instances of one class -- harmless, but lockdep cannot tell). */ if (sc->listen.backlog != -1) { /* was a listener */ + /* + * We don't move them to the orphaned list here, + * each child does that itself at the end of its + * smbdirect_socket_cleanup_work(), see + * smbdirect_listen_orphan_socket(). Only that checks + * accept.listener, which is still NULL for a child + * that smbdirect_listen_connect_request() is still + * setting up and will release itself on failure. + */ spin_lock_irqsave(&sc->listen.lock, flags); list_splice_init(&sc->listen.ready, &sc->listen.pending); list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list) @@ -486,6 +504,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work) * in order to notice the broken connection. */ smbdirect_socket_wake_up_all(sc); + + /* + * If we're still on the pending or ready list + * of a listener, we started the disconnect + * as far as possible above, so we move ourself + * to the orphaned list of the listener, + * which will release us. + * + * This is a no-op internally if + * sc->accept.listener is NULL. + */ + smbdirect_listen_orphan_socket(sc); } static void smbdirect_socket_destroy(struct smbdirect_socket *sc) @@ -544,6 +574,7 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) disable_work_sync(&sc->recv_io.posted.refill_work); disable_work_sync(&sc->idle.immediate_work); disable_delayed_work_sync(&sc->idle.timer_work); + disable_work_sync(&sc->listen.purge_orphaned_work); if (sc->rdma.cm_id) rdma_lock_handler(sc->rdma.cm_id); @@ -607,6 +638,19 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) spin_lock_irqsave(&sc->listen.lock, flags); list_splice_tail_init(&sc->listen.ready, &pending_list); list_splice_tail_init(&sc->listen.pending, &pending_list); + /* + * purge_orphaned_work is already disabled above, + * so we also need to release the orphaned sockets. + * + * Clearing accept.listener under listen.lock + * makes us responsible for releasing them and + * prevents them from moving themselves to + * the orphaned list via + * smbdirect_listen_orphan_socket(). + */ + list_splice_tail_init(&sc->listen.orphaned, &pending_list); + list_for_each_entry(psc, &pending_list, accept.list) + WRITE_ONCE(psc->accept.listener, NULL); spin_unlock_irqrestore(&sc->listen.lock, flags); /* It's not possible for upper layer to get to reassembly */ @@ -650,7 +694,6 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc) "release %zu pending sockets\n", psockets); list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) { list_del_init(&psc->accept.list); - psc->accept.listener = NULL; smbdirect_socket_release(psc); } if (sc->listen.backlog != -1) /* was a listener */ @@ -777,7 +820,17 @@ static void smbdirect_socket_release_destroy(struct kref *kref) * in DESTROYED state, before we free the memory. */ smbdirect_socket_destroy_sync(sc); - kfree(sc); + + /* + * Only a listener (backlog != -1) is ever dereferenced + * via sc->accept.listener under rcu_read_lock(), see + * smbdirect_listen_orphan_socket(). Other sockets can be + * freed immediately. + */ + if (sc->listen.backlog != -1) /* was a listener */ + kfree_rcu(sc, refs.rcu); + else + kfree(sc); } void smbdirect_socket_release(struct smbdirect_socket *sc) diff --git a/fs/smb/smbdirect/socket.h b/fs/smb/smbdirect/socket.h index c09eddd8ad16..14114a2567a2 100644 --- a/fs/smb/smbdirect/socket.h +++ b/fs/smb/smbdirect/socket.h @@ -151,6 +151,12 @@ struct smbdirect_socket { * the disconnect refcount. */ struct kref destroy; + + /* + * smbdirect_socket_release_destroy() uses + * kfree_rcu(), see accept.listener. + */ + struct rcu_head rcu; } refs; /* RDMA related */ @@ -216,6 +222,16 @@ struct smbdirect_socket { * only be > 0. */ int backlog; + /* + * Sockets on pending or ready that failed + * move themselves to orphaned and queue + * purge_orphaned_work, which releases them. + * So that they are freed while the listener + * is still alive. They still count against + * the backlog until they are released. + */ + struct list_head orphaned; + struct work_struct purge_orphaned_work; } listen; /* @@ -226,7 +242,30 @@ struct smbdirect_socket { * connection. */ struct { + /* + * This is only set, protected by + * listener->listen.lock, while the socket + * is owned by the listener (on its pending, + * ready or orphaned list). The one who clears + * it is responsible for releasing the socket. + * + * The memory of a struct smbdirect_socket is + * freed via kfree_rcu(), so the listener + * can be dereferenced under rcu_read_lock(), + * even if accept.listener is cleared and + * the listener is released concurrently. + */ struct smbdirect_socket *listener; + /* + * Linkage on one of the listener's + * listen.{pending,ready,orphaned} lists. + * + * accept.list and accept.listener are set + * together under listener->listen.lock before + * smbdirect_accept_connect_request() is called, + * so whenever the socket is on one of those lists + * accept.listener is that listener (never NULL). + */ struct list_head list; } accept; @@ -588,6 +627,9 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc) spin_lock_init(&sc->listen.lock); INIT_LIST_HEAD(&sc->listen.pending); INIT_LIST_HEAD(&sc->listen.ready); + INIT_LIST_HEAD(&sc->listen.orphaned); + INIT_WORK(&sc->listen.purge_orphaned_work, __smbdirect_socket_disabled_work); + disable_work_sync(&sc->listen.purge_orphaned_work); sc->listen.backlog = -1; /* not a listener */ init_waitqueue_head(&sc->listen.wait_queue); -- 2.43.0 From metze at samba.org Fri Oct 9 10:02:13 2026 From: metze at samba.org (Stefan Metzmacher) Date: Fri, 9 Oct 2026 12:02:13 +0200 Subject: [PATCH v4 3/3] smb: smbdirect: don't hand out already failed sockets in smbdirect_socket_accept() In-Reply-To: References: Message-ID: <71ecbb828da5a3fa2c82491697922b7091d455be.1791540051.git.metze@samba.org> A socket on the ready list is in SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, but it may fail before smbdirect_socket_accept() picks it up, e.g. RDMA_CM_EVENT_DISCONNECTED already moved it to SMBDIRECT_SOCKET_DISCONNECTED. smbdirect_socket_accept() unconditionally overwrote the status with SMBDIRECT_SOCKET_CONNECTED and handed out the already disconnected socket. smbdirect_socket_cleanup_work() then called rdma_disconnect() on it again, and before the recent commit smbdirect_socket_destroy_sync() waited forever for an RDMA_CM_EVENT_DISCONNECTED that already happened. Now we check first_error and change the status to SMBDIRECT_SOCKET_CONNECTED only via cmpxchg() from SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, while we still hold listen.lock. If the socket already failed, we move it to the orphaned list and queue purge_orphaned_work, like smbdirect_listen_orphan_socket() does, and try the next one. If we only found failed sockets, we wait for the next one. Fixes: dc691b91ad16 ("smb: smbdirect: introduce smbdirect_socket_{listen,accept}()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1791224972.git.metze%40samba.org Closes: https://sashiko.dev/#/bug/linux-f9dbca09-30a7-4145-9bdb-4676759982c8 Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 84 +++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index 977144442974..e7e5eb75a5ae 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -836,7 +836,11 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long if (ret < 0) return ret; - return 0; + /* + * Return the remaining timeout, so the caller can carry it + * over to the next smbdirect_socket_wait_for_accept() call. + */ + return ret; } struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, @@ -846,6 +850,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, struct smbdirect_socket *nsc; unsigned long flags; +again: if (lsc->status != SMBDIRECT_SOCKET_LISTENING) { arg->err = -EINVAL; return NULL; @@ -857,7 +862,7 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } if (list_empty_careful(&lsc->listen.ready)) { - int ret; + long ret; if (timeo == 0) { arg->err = -EAGAIN; @@ -865,16 +870,64 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } ret = smbdirect_socket_wait_for_accept(lsc, timeo); - if (ret) { + if (ret < 0) { arg->err = ret; return NULL; } + /* + * Carry the remaining timeout over, so that a stream of + * failed connections that we orphan and skip (goto again) + * can't reset the caller's timeout and wait forever. + */ + timeo = ret; } spin_lock_irqsave(&lsc->listen.lock, flags); - nsc = list_first_entry_or_null(&lsc->listen.ready, - struct smbdirect_socket, - accept.list); + /* + * Recheck under the lock. + */ + if (lsc->status != SMBDIRECT_SOCKET_LISTENING) { + arg->err = -EINVAL; + spin_unlock_irqrestore(&lsc->listen.lock, flags); + return NULL; + } + if (lsc->first_error) { + arg->err = lsc->first_error; + spin_unlock_irqrestore(&lsc->listen.lock, flags); + return NULL; + } + + while ((nsc = list_first_entry_or_null(&lsc->listen.ready, + struct smbdirect_socket, + accept.list))) { + /* + * nsc may have failed after it was moved + * to the ready list, e.g. RDMA_CM_EVENT_DISCONNECTED + * already moved it to SMBDIRECT_SOCKET_DISCONNECTED. + * We must not overwrite that with + * SMBDIRECT_SOCKET_CONNECTED and hand + * out an already disconnected socket. + * + * Doing this under listen.lock means nsc + * still belongs to us, so we can just move + * a failed socket to the orphaned list, + * like smbdirect_listen_orphan_socket() does, + * and try the next one. + */ + if (!READ_ONCE(nsc->first_error) && + cmpxchg(&nsc->status, + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING, + SMBDIRECT_SOCKET_CONNECTED) == + SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) + break; + + smbdirect_log_rdma_event(nsc, SMBDIRECT_LOG_INFO, + "orphaning failed socket status=%s first_error=%1pe\n", + smbdirect_socket_status_string(nsc->status), + SMBDIRECT_DEBUG_ERR_PTR(nsc->first_error)); + list_move_tail(&nsc->accept.list, &lsc->listen.orphaned); + queue_work(lsc->workqueues.cleanup, &lsc->listen.purge_orphaned_work); + } if (nsc) { WRITE_ONCE(nsc->accept.listener, NULL); list_del_init_careful(&nsc->accept.list); @@ -882,8 +935,13 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, } spin_unlock_irqrestore(&lsc->listen.lock, flags); if (!nsc) { - arg->err = -EAGAIN; - return NULL; + /* + * If we only found failed sockets or no socket, + * we wait for the next one. + * + * A possible -EAGAIN/-ETIMEOUT is handled above. + */ + goto again; } /* @@ -892,12 +950,14 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc, * so it didn't grant any credits to us. * * The caller expects a connected socket - * now as there are no credits anyway. + * now as there are no credits anyway, + * above we already changed to SMBDIRECT_SOCKET_CONNECTED + * under the lsc->listen.lock and with cmpxchg. * - * Then we send the negotiation response in - * order to grant credits to the peer. + * Now we send the negotiation response in + * order to grant credits to the peer, + * as the socket is now visible to the application layer. */ - nsc->status = SMBDIRECT_SOCKET_CONNECTED; smbdirect_accept_negotiate_finish(nsc, 0); return nsc; -- 2.43.0 From metze at samba.org Fri Oct 9 10:28:27 2026 From: metze at samba.org (Stefan Metzmacher) Date: Fri, 9 Oct 2026 12:28:27 +0200 Subject: [PATCH] smb: smbdirect: don't WARN on an unexpected receive opcode Message-ID: <20261009102827.1785085-1-metze@samba.org> The receive completion handlers smbdirect_connection_recv_io_done(), smbdirect_accept_negotiate_recv_done() and smbdirect_connect_negotiate_recv_done() verify that the completion is a regular receive with WARN_ON_ONCE(wc->opcode != IB_WC_RECV). But wc->opcode is controlled by the remote peer: a posted receive work request is consumed not only by an incoming SEND, but also by an RDMA Write with Immediate, which completes as IB_WC_RECV_RDMA_WITH_IMM. An unauthenticated peer can transmit such a message, even before the negotiate completes, so it reaches the accept/connect negotiate handlers as well. With panic_on_warn set this is a remote denial of service, and otherwise it still taints the kernel and spams the log for something a peer can trigger at will. The opcode check itself was inherited from the original client and server recv_done() handlers (fs/smb/client/smbdirect.c and fs/smb/server/transport_rdma.c), where an unexpected opcode was simply logged and the connection was torn down. Only the WARN_ON_ONCE() was added on top during the socketization rework. Drop the WARN_ON_ONCE() and keep the existing graceful error path, which logs the unexpected opcode (unless the status is IB_WC_WR_FLUSH_ERR) and tears the connection down. Fixes: ea4151222a93 ("smb: smbdirect: introduce smbdirect_connection_recv_io_done()") Fixes: eb3ed1e9048c ("smb: smbdirect: introduce smbdirect_accept_connect_request()") Fixes: d1f187656797 ("smb: smbdirect: introduce smbdirect_connect[_sync]()") Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-5341a0c0-7b00-4eef-915c-91e099649d3e Cc: Namjae Jeon Cc: Paulo Alcantara Cc: Tom Talpey Cc: linux-cifs at vger.kernel.org Cc: samba-technical at lists.samba.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Stefan Metzmacher --- fs/smb/smbdirect/accept.c | 2 +- fs/smb/smbdirect/connect.c | 2 +- fs/smb/smbdirect/connection.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/smbdirect/accept.c b/fs/smb/smbdirect/accept.c index e7e5eb75a5ae..85e37d11f43d 100644 --- a/fs/smb/smbdirect/accept.c +++ b/fs/smb/smbdirect/accept.c @@ -234,7 +234,7 @@ static void smbdirect_accept_negotiate_recv_done(struct ib_cq *cq, struct ib_wc struct smbdirect_socket *sc = recv_io->socket; unsigned long flags; - if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) { + if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) { if (wc->status != IB_WC_WR_FLUSH_ERR) smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR, "wc->status=%s (%d) wc->opcode=%d\n", diff --git a/fs/smb/smbdirect/connect.c b/fs/smb/smbdirect/connect.c index 34a3e72c38fb..0d02f03063ac 100644 --- a/fs/smb/smbdirect/connect.c +++ b/fs/smb/smbdirect/connect.c @@ -581,7 +581,7 @@ static void smbdirect_connect_negotiate_recv_done(struct ib_cq *cq, struct ib_wc struct smbdirect_socket *sc = recv_io->socket; unsigned long flags; - if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) { + if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) { if (wc->status != IB_WC_WR_FLUSH_ERR) smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR, "wc->status=%s (%d) wc->opcode=%d\n", diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index f48c857bda5a..e6c7b343fe87 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -1529,7 +1529,7 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc) u32 data_length; u32 remaining_data_length; - if (unlikely(wc->status != IB_WC_SUCCESS || WARN_ON_ONCE(wc->opcode != IB_WC_RECV))) { + if (unlikely(wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_RECV)) { if (wc->status != IB_WC_WR_FLUSH_ERR) smbdirect_log_rdma_recv(sc, SMBDIRECT_LOG_ERR, "wc->status=%s (%d) wc->opcode=%d\n", -- 2.43.0 From linkinjeon at kernel.org Fri Oct 9 12:46:23 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Fri, 9 Oct 2026 21:46:23 +0900 Subject: [PATCH] smb: smbdirect: don't WARN on an unexpected receive opcode In-Reply-To: <20261009102827.1785085-1-metze@samba.org> References: <20261009102827.1785085-1-metze@samba.org> Message-ID: On Fri, Oct 9, 2026 at 7:28?PM Stefan Metzmacher wrote: > > The receive completion handlers smbdirect_connection_recv_io_done(), > smbdirect_accept_negotiate_recv_done() and > smbdirect_connect_negotiate_recv_done() verify that the completion is a > regular receive with WARN_ON_ONCE(wc->opcode != IB_WC_RECV). > > But wc->opcode is controlled by the remote peer: a posted receive work > request is consumed not only by an incoming SEND, but also by an RDMA > Write with Immediate, which completes as IB_WC_RECV_RDMA_WITH_IMM. An > unauthenticated peer can transmit such a message, even before the > negotiate completes, so it reaches the accept/connect negotiate > handlers as well. With panic_on_warn set this is a remote denial of > service, and otherwise it still taints the kernel and spams the log for > something a peer can trigger at will. > > The opcode check itself was inherited from the original client and > server recv_done() handlers (fs/smb/client/smbdirect.c and > fs/smb/server/transport_rdma.c), where an unexpected opcode was simply > logged and the connection was torn down. Only the WARN_ON_ONCE() was > added on top during the socketization rework. Drop the WARN_ON_ONCE() > and keep the existing graceful error path, which logs the unexpected > opcode (unless the status is IB_WC_WR_FLUSH_ERR) and tears the > connection down. > > Fixes: ea4151222a93 ("smb: smbdirect: introduce smbdirect_connection_recv_io_done()") > Fixes: eb3ed1e9048c ("smb: smbdirect: introduce smbdirect_accept_connect_request()") > Fixes: d1f187656797 ("smb: smbdirect: introduce smbdirect_connect[_sync]()") > Reported-by: Sashiko > Closes: https://sashiko.dev/#/bug/linux-5341a0c0-7b00-4eef-915c-91e099649d3e > Cc: Namjae Jeon > Cc: Paulo Alcantara > Cc: Tom Talpey > Cc: linux-cifs at vger.kernel.org > Cc: samba-technical at lists.samba.org > Assisted-by: Claude:claude-opus-5-5 > Signed-off-by: Stefan Metzmacher Applied it to #ksmbd-for-next. Thanks! From linkinjeon at kernel.org Fri Oct 9 12:46:46 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Fri, 9 Oct 2026 21:46:46 +0900 Subject: [PATCH v4 0/3] smb: smbdirect: fix listener backlog leak and teardown races In-Reply-To: References: Message-ID: On Fri, Oct 9, 2026 at 7:02?PM Stefan Metzmacher wrote: > > Hi Namjae, > > These fix a set of problems in the shared smbdirect module > (fs/smb/smbdirect/), reached through the listener/accept path, which in > practice is driven by ksmbd for incoming SMB Direct connections. > > The main one is a pre-authentication remote denial of service: the > listener keeps every accepted connection on a fixed-size backlog > (listen.pending, limit 10 for ksmbd). A connection is only removed from > that backlog on the success path (promotion to listen.ready after a > completed negotiate, or dequeue by accept()). A connection that is > accepted at the RDMA level but then fails before it is accepted by the > upper layer - negotiate timeout, peer disconnect, or an invalid > negotiate request - is never removed, so its backlog slot is leaked. > After about ten such events the listener rejects every new connection > with -EBUSY and SMB Direct stays unusable until the service is > restarted. An unauthenticated peer can trigger this with ~10 aborted > connections. > > This was reported by Lee Seong Hyeon : > > https://lore.kernel.org/linux-cifs/CANTrAmxL5sWU8Sn29JAGZvSq5PBB2OA+VKA0MHsZJMesagtfzA at mail.gmail.com/ > > The series: > > - smbdirect_socket_destroy_sync() no longer waits for > RDMA_CM_EVENT_DISCONNECTED after rdma_disconnect(). That wait could > take very long (until the rdma cm gives up, e.g. a peer that just > vanished) or never complete if the event already happened and the > status was overwritten. rdma_destroy_id() in > smbdirect_socket_destroy() is enough to stop further events; the > rest of the disconnect protocol is handled by the rdma core > asynchronously. This also makes releasing orphaned sockets > (next patch) non-blocking. > > - A failed, not-yet-accepted child of a listener now moves itself to a > new listen.orphaned list at the end of its cleanup work and queues > listen.purge_orphaned_work, which releases it, so it no longer leaks > its backlog slot (nor its struct sock / RDMA resources) for the > lifetime of the listener. sc->accept.listener is only changed under > listener->listen.lock, and whoever clears it owns the release; > the listener is freed via kfree_rcu() so a child can dereference it > under rcu_read_lock(). This is the DoS fix. > > - smbdirect_socket_accept() no longer hands out a socket that failed > after it was put on the ready list (e.g. the peer disconnected in > the meantime); it checks first_error / the status under listen.lock, > orphans such a socket and tries the next one. And it also > no longer returns -EAGAIN if a non zero timeout was passed. > > I tested the reproducer and the problem is fixed > and I run various xfstests. > > I think these are important and should go into 7.3 > > Changes since v3: > (https://lore.kernel.org/r/cover.1791311817.git.metze at samba.org/) > > - "smb: smbdirect: release failed pending sockets of a listener": > install the real event handler and the expected event at the very > start of smbdirect_accept_connect_request(), before anything that can > fail. On an early error the caller only schedules nsc's deferred > cleanup and returns 0, so the rdma_cm core keeps the cm_id - it must > then already carry smbdirect_accept_rdma_event_handler(), which turns > any unexpected event (e.g. RDMA_CM_EVENT_DEVICE_REMOVAL) into a clean > smbdirect_socket_schedule_cleanup(). Previously the placeholder > smbdirect_socket_rdma_event_handler() left by > smbdirect_socket_init_accepting() stayed installed on that path and > returned -ESTALE, making the rdma_cm core destroy the cm_id behind the > back of the pending teardown. > > - smbdirect_socket_accept always goes back to smbdirect_socket_wait_for_accept > when no valid new socket was found, so that we don't return -EAGAIN > unless a zero timeout was given. > > Changes since v2: > (https://lore.kernel.org/r/cover.1791229120.git.metze at samba.org/) > > - "smb: smbdirect: release failed pending sockets of a listener": > reworked the connect-request error handling after more Sashiko > review. smbdirect_accept_connect_request() no longer tears down any > RDMA state or clears the cm_id on failure; it only returns a > not-yet-posted recv_io to the pool and returns the error. > smbdirect_listen_connect_request() now sets nsc->accept.listener and > publishes nsc on the pending list *before* it calls > smbdirect_accept_connect_request(), and on a non-zero return it > schedules nsc's teardown with smbdirect_socket_schedule_cleanup() and > always returns 0, so the rdma_cm core keeps the connection id and nsc > owns it (and destroys it in its own deferred teardown). The teardown > must be deferred because nsc's id_priv->handler_mutex is held by the > rdma_cm core across the CONNECT_REQUEST handler; that same lock (and > the listener's) also guarantees nsc can't go away under us. This > closes the race Sashiko reported, where a connection that established > between rdma_accept() and setting the listener could be left stranded > on the pending list. > > Changes since v1: > (https://lore.kernel.org/r/cover.1791224972.git.metze at samba.org) > > - "smb: smbdirect: release failed pending sockets of a listener": > fix a use-after-free and a missed-orphan race in > smbdirect_listen_connect_request(), spotted by Sashiko. The > nsc->first_error check and the orphaning are now done under > listen.lock, together with setting nsc->accept.listener, so a > concurrent smbdirect_socket_cleanup_work()/purge_orphaned_work() > can neither free nsc while we still look at it, nor miss the > orphaning and leak it on the pending list. > > - "smb: smbdirect: don't hand out already failed sockets in > smbdirect_socket_accept()": bound the accept() wait across the > "skip a failed socket and try the next one" retries, also spotted > by Sashiko. smbdirect_socket_wait_for_accept() now returns the > remaining timeout and smbdirect_socket_accept() carries it over, so > a stream of failed connections can no longer reset the caller's > timeout. ksmbd passes MAX_SCHEDULE_TIMEOUT, so it is unaffected in > practice. > > - Added Reported-by:/Closes: for the Sashiko reviews. > > Stefan Metzmacher (3): > smb: smbdirect: don't wait for RDMA_CM_EVENT_DISCONNECTED in > smbdirect_socket_destroy_sync() > smb: smbdirect: release failed pending sockets of a listener > smb: smbdirect: don't hand out already failed sockets in > smbdirect_socket_accept() Applied them to #ksmbd-for-next. Thanks! From tom at talpey.com Fri Oct 9 16:36:16 2026 From: tom at talpey.com (Tom Talpey) Date: Fri, 9 Oct 2026 12:36:16 -0400 Subject: [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races In-Reply-To: <6981e5e5-e856-4613-a3fb-c8cdc7a9219d@talpey.com> References: <6981e5e5-e856-4613-a3fb-c8cdc7a9219d@talpey.com> Message-ID: <2cae5f8d-c07f-4778-a890-dcfa5aeb13ad@talpey.com> On 10/5/2026 9:34 PM, Tom Talpey wrote: > FYI I am hoping to be able to test these and tge others Tuesday (tomorrow) to verify they fix the issues we saw at IOLab. Some were not available when the test network shut down. FYI that I'm not having much luck getting the tests running here, the Network Direct DDK appears to need some sort of update which I can't locate, so the test suite fails to build on my setup, for now. Tom. > > Tom. > > Oct 5, 2026 9:23:57 PM Namjae Jeon : > >> On Tue, Oct 6, 2026 at 9:19?AM ??? wrote: >>> >>> [PATCH v2 0/3] smb: smbdirect: fix listener backlog leak and teardown races >>> >>> Hi Stefan, >>> >>> Thanks a lot for the fix and for crediting me. >>> >>> Could you please use my real name instead of the display name in the >>> Reported-by: tags (patch 2/3) and in the cover letter, i.e.: >>> >>> ? Reported-by: Lee Seong Hyeon >>> >>> The address stays the same. If it is easier for you, I'm also fine with >>> you fixing it up when applying, or with Namjae doing it. >> Okay, I will directly update it when applying this patch-set. >> >> Thanks. > > From pc at manguebit.org Sat Oct 10 17:05:27 2026 From: pc at manguebit.org (Paulo Alcantara) Date: Sat, 10 Oct 2026 14:05:27 -0300 Subject: [PATCH 0/7] smb: client: fix OOB and use-after-free infoleaks in SMB1 FIND responses In-Reply-To: <20260929091636.2618227-1-diego@bynar.io> References: <20260929091636.2618227-1-diego@bynar.io> Message-ID: Diego Oliva writes: > The SMB1 client parses TRANS2_FIND_FIRST2 and TRANS2_FIND_NEXT2 > responses without checking them against the number of bytes it > received. A malicious or compromised server can make the client read > past the end of a response, and past the end of the cifs_request > buffer holding it, and send what it read back to the server. Both the > out-of-bounds reads and the use-after-free read are information leaks: > the bytes leave the machine in the client's next FindNext request, > from a position the server picks: > > - LastNameOffset, which the client uses to locate the last entry of > a response, is only checked against CIFSMaxBufSize, never against > the response that was actually received, so psrch_inf->last_entry > can be placed past the end of the allocation. > - cifs_save_resume_key() parses the entry there without a bound. The > name length comes from the entry itself, and for SMB_FIND_FILE_UNIX > from a NUL scan of up to PATH_MAX + 1 UTF-16 units. The name is > recorded as the resume name of the search, and the next > CIFSFindNext() rejects only a recorded length of PATH_MAX or more > before copying the name into its request and sending it, so up to > 4 KiB of memory from beyond the response leaves the machine, > together with the resume key read from the same entry. > - When the last entry is rejected, and when a search rewind frees > the buffer without its FindFirst recording a new name, the resume > name keeps pointing into a buffer that has been released, and the > next CIFSFindNext() copies it from there. > - The response parameters (search handle, entry count, end-of-search > flag, LastNameOffset) and the data area are read at offsets that > validate_t2() caps at 1024. It bounds the parameter and data > counts by the byte count of the response, but nothing ties either > area to the bytes that were received, so one can begin or end past > them; those reads stay inside the allocation but can return stale > bytes. > > A FindNext response that reports no entries without ending the search > is enough to make the client send the resume name, and each further > response can do the same. Reaching the code needs a malicious or > compromised server, an explicit vers=1.0 mount of it > (CONFIG_CIFS_ALLOW_INSECURE_LEGACY, default y) and a directory listing > on that mount; SMB1 is not negotiated by default. The same code is in > every maintained stable tree. > > Commit f8cf09a53a0d ("smb: client: bound dirent name against end of > SMB response in cifs_filldir"), in v7.2, bounded the name of the > entries cifs_readdir() emits, after the parse; the resume-key path was > left unbounded and the parse itself still ran before any bound. > > The fix is one change conceptually, but as a single patch it is well > over the 100 lines with context that stable-kernel-rules.rst allows, > so stable could not take it. This series splits it into patches that > each fix one thing, build and pass checkpatch on their own, and stay > under that limit. > > What each patch fixes, and what to backport: > > 1/7 fix use-after-free infoleak via the readdir resume name > The use-after-free. It comes first so that the patches after it > cannot widen the window it closes. Also resets resume_key with > the name and skips the zero-length copy in CIFSFindNext(). > Tagged Cc: stable. > 2/7 fix OOB last_entry pointer from unbounded LastNameOffset > The out-of-bounds pointer, bounded against the response that > was received rather than against the declared data area. It > applies on top of 1/7, whose resets sit in its context and > which makes the paths this patch sends more responses down > safe. Also records the search handle before the last entry is > examined, because the new no-entries case would otherwise lose > the handle. Cc: stable. > 3/7 fix OOB read of the resume name sent in TRANS2_FIND_NEXT2 > Bounds the entry parse in cifs_fill_dirent() for both callers, > before anything is read. This is what stops a name from being > recorded and sent from past the end of the response. > Tagged Cc: stable. > 4/7 fix OOB read in the SMB_FIND_FILE_UNIX name scan > Caps the NUL scan at the response end; without it the scan > still runs several KiB past the allocation. Depends on 3/7 for > the end of the response. A scan that reaches the end of the > response without finding a terminator is rejected; a name > longer than the PATH_MAX cap of the scans is still truncated > there, as before. Cc: stable. > 5/7 reject FIND data areas that run past the received response > Hardening rather than memory safety: it stops entries being > parsed from stale bytes inside the allocation, and bounds the > data area that the nxt_dir_entry() walk starts from and that > the two query helpers read their entry from. It rejects with > -EINVAL, as validate_t2() does, logging with cifs_dbg(), and > adds no tracepoint, so it applies to trees before 6.19. > Cc: stable. > 6/7 reject short or out-of-range FIND response parameters > The parameters are read inside the allocation, but the stale > search handle among them is sent back to the server. Depends on > 5/7, whose declarations it extends. It returns smb_EIO2() with > two new smb_eio_traces values; backports before v6.19 need > -EINVAL instead. Cc: stable. > 7/7 drop the redundant name bounds in cifs_filldir() > Cleanup of the two post-parse checks that 3/7 made redundant. > Depends on 3/7. Not for stable. > > No patch needs one that follows it, so the series can be cut after any > patch. Patches 1/7 to 4/7 are every out-of-bounds and use-after-free > fix; 5/7 stands on its own; 6/7 can follow where smb_EIO2() exists. > Trees before v7.2 need the adjustments noted in the patches that need > them. > > cifs_fill_dirent() is shared with the SMB2 readdir path; 3/7 and 4/7 > add checks there that a valid SMB2 response cannot fail, since > smb2_parse_query_directory() already walks its entries against the > end of the response. > > Left out on purpose: an entry-size check for cifs_query_path_info() > and cifs_backup_query_path_info(), which still read one whole entry > from a data area that 5/7 bounds only as a whole (those reads stay > inside the allocation); passing the end to posix_info_parse() in > cifs_fill_dirent_posix(), which only SMB3.1.1 POSIX mounts reach, > with entries that num_entries() has already validated; and a lower > bound on ParameterOffset and DataOffset, since reads at a low offset > stay inside the received response. > > Each patch builds with W=1 without warnings and passes checkpatch. > The out-of-bounds reads and the use-after-free read were reproduced > against a test server with KASAN enabled, by listing a directory on > an SMB1 mount; the splats are in 1/7, 2/7, 3/7 and 4/7, all taken on > the unpatched tree. The one in 4/7 shows the case the preceding > patches leave open, where the entry starts inside the response and > the scan still leaves the allocation. > > The series is based on cifs-next at commit f14572c203d5 ("Merge tag > 'cifs-fixes-7.3-rc5' of https://git.manguebit.org/linux"). The files it > touches are identical there and in v7.3-rc4. > ... Applied. From bjacke at samba.org Mon Oct 12 18:42:53 2026 From: bjacke at samba.org (=?iso-8859-1?Q?Bj=F6rn?= JACKE) Date: Mon, 12 Oct 2026 20:42:53 +0200 Subject: [Announce] Samba 4.24.8 Available for Download Message-ID: Release Announcements --------------------- This is the latest stable release of the Samba 4.24 release series. Changes since 4.24.7 -------------------- o Volker Lendecke * BUG 16200: generate_unique_u64() may not protect against veto_value * BUG 16261: Rename with ReplaceIfExists to a target differing only in case creates a duplicate instead of replacing (regression in bb130f32) o Stefan Metzmacher * BUG 15962: SMB3 SESSION SETUP responses must always be signed * BUG 16081: winbindd_child_msg_filter: talloc_get_type_abort crashes when winbind max domain connections > 1 * BUG 16200: generate_unique_u64() may not protect against veto_value o Kopylov Pavel * BUG 16239: Fix parsing of uppercase "0X" hex values in the "kdc default domain supported enctypes" smb.conf parameter. o Anoop C S * BUG 16240: smbd temporary mkdir name can exceed NAME_MAX for otherwise valid client names * BUG 16258: Python smbconf transaction_start() lacks a talloc stack frame with CTDB o Martin Schwenke * BUG 16254: CTDB database operations can be run against incorrect nodes * BUG 16273: A node can be banned because it tries to run recovery after losing the cluster lock o Shweta Sodani * BUG 15857: All backend log should honor global log level "log level = " ####################################### Reporting bugs & Development Discussion ####################################### Please discuss this release on the samba-technical mailing list or by joining the #samba-technical:matrix.org matrix room, or #samba-technical IRC channel on irc.libera.chat. If you do report problems then please try to send high quality feedback. If you don't provide vital information to help us track down the problem then you will probably be ignored. All bug reports should be filed under the Samba 4.1 and newer product in the project's Bugzilla database (https://bugzilla.samba.org/). ====================================================================== == Our Code, Our Bugs, Our Responsibility. == The Samba Team ====================================================================== ================ Download Details ================ The uncompressed tarballs and patch files have been signed using GnuPG (ID AA99442FB680B620). The source code can be downloaded from: https://download.samba.org/pub/samba/stable/ The release notes are available online at: https://www.samba.org/samba/history/samba-4.24.8.html Our Code, Our Bugs, Our Responsibility. (https://bugzilla.samba.org/) --Enjoy The Samba Team -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: not available URL: From pc at manguebit.org Mon Oct 12 19:12:47 2026 From: pc at manguebit.org (Paulo Alcantara) Date: Mon, 12 Oct 2026 16:12:47 -0300 Subject: [PATCH] smb: client: add "noautomount" mount option to skip junction submounts In-Reply-To: References: Message-ID: ???? writes: > None of the existing options turn this off: "nodfs" only skips DFS > referral resolution (these junction submounts are not DFS), and > "reparse=" selects the format used when creating new reparse points, > not how existing ones are traversed. > > Add a "noautomount" mount option. When set, CIFS_FATTR_JUNCTION inodes > are not marked S_AUTOMOUNT, so junctions stay regular directories and > the SMB server resolves the reparse target on access - which Windows > does transparently for normal path traversal. The default is off, so > behavior without the option is completely unchanged. Note that the > option also disables automounting of DFS referrals, so it should not be > used on DFS mounts. Instead of introducing a new mount option, can't you use 'noserverino' to handle such cases? That is, if you mount the share with 'noserverino' mount option and the client finds a directory with a name-surrogate reparse point that it can't handle, then neither create fake attrs nor make them as automounts. The original intent of marking those as automounts was to prevent inode collisions when accessing files from those directories and then leaving the parent mount still trusting inode numbers from the server, as way to keep hardlinks working on the parent mount. From linkinjeon at kernel.org Wed Oct 14 01:48:20 2026 From: linkinjeon at kernel.org (Namjae Jeon) Date: Wed, 14 Oct 2026 10:48:20 +0900 Subject: [PATCH 1/1] cifs: fix buffer overflow in SMB2_tcon In-Reply-To: <0f3d431d65a5f46c0487293d127e748775ca85b8.1791865501.git.rakukuip@gmail.com> References: <0f3d431d65a5f46c0487293d127e748775ca85b8.1791865501.git.rakukuip@gmail.com> Message-ID: On Wed, Oct 14, 2026 at 2:16?AM Ren Wei wrote: > > From: Luxiao Xu > > In SMB2_tcon(), unc_path is allocated with a fixed buffer size of > MAX_SHARENAME_LENGTH * 2 bytes (672 bytes). However, the user-controlled > UNC path (tree) length is passed to cifs_strtoUTF16(), which assumes the > destination buffer is large enough and writes strlen(tree) + 1 UTF-16 code > units. > > Commit 62a1a439e0fd ("cifs: clean up handling of unc= option") removed the > length check on UNC mount options, allowing mount paths to exceed > MAX_SHARENAME_LENGTH while retaining the fixed-size buffer allocation in > SMB2_tcon(). Consequently, an oversized UNC string causes cifs_strtoUTF16() > to write past the end of the heap object, resulting in a slab-out-of-bounds > write. > > Fix this by dynamically allocating unc_path based on the length of tree so > that it safely accommodates all UTF-16 code units. > > Fixes: 62a1a439e0fd ("cifs: clean up handling of unc= option") > Cc: stable at vger.kernel.org > Reported-by: Xin Liu > Assisted-by: LLM > Signed-off-by: Luxiao Xu > Signed-off-by: Ren Wei Reviewed-by: Namjae Jeon Thanks. From derek at limbic.systems Wed Oct 14 16:02:56 2026 From: derek at limbic.systems (Derek Perez) Date: Wed, 14 Oct 2026 09:02:56 -0700 Subject: vfs_full_audit: offload_read/offload_write (SMB2 copy-chunk) log no file names Message-ID: Hello, We use vfs_full_audit as the file-activity audit trail on a small office file server and found one gap we would like to ask about before proposing anything. When a Windows client copies a file within the server (Explorer, robocopy, PowerShell Copy-Item all use FSCTL_SRV_COPYCHUNK), full_audit logs the four offload operations with an empty argument field, so the audit shows who opened the source and who created the destination, but not that data moved from one to the other: audit|alice|192.0.2.4|firm|create_file|ok|0x17019f|file|overwrite_if|/srv/firm/Copies/scan.pdf audit|alice|192.0.2.4|firm|offload_read_send|ok| audit|alice|192.0.2.4|firm|offload_read_recv|ok| audit|alice|192.0.2.4|firm|strict_lock_check|ok|/srv/firm/Clients/Smith/scan.pdf:0-1048576:0 audit|alice|192.0.2.4|firm|strict_lock_check|ok|/srv/firm/Copies/scan.pdf:0-1048576:1 audit|alice|192.0.2.4|firm|offload_write_send|ok| audit|alice|192.0.2.4|firm|offload_write_recv|ok| (repeated per 1 MiB chunk; 8 offload_write_send lines for an 8 MiB file) Setup: Samba 4.23.10 on NixOS, full_audit:prefix = audit|%U|%I|%S, Windows Server 2025 client, Shell.Application CopyHere of an 8 MiB file within one share. The same pattern appears for robocopy, Copy-Item, and a move between two shares on the same server. The excerpt is from a test lab; the names are synthetic, the paths are shortened, and the client address is replaced. Looking at source3/modules/vfs_full_audit.c on master (21f8b3398bb8, 2026-10-14) and in 4.23.10, smb_full_audit_offload_read_send() and smb_full_audit_offload_write_send() call do_log() with "" although the source fsp (read_send) and dest_fsp, dest_off and num (write_send) are available as arguments. strict_lock_check already logs fsp_str_do_log(fsp) with the range, which is how the pairing above becomes visible, but logging strict_lock_check on every read and write is too much volume for us to keep enabled. Questions: 1. Would a change that logs the file name in the offload operations be acceptable, for example, offload_read_send printing the source fsp name plus offset and length, and offload_write_send printing the destination fsp name plus dest_off and num? The source name for write_send would come from the read side through the token, so the read_send line plus the write_send line would together describe the copy. 2. Is there a reason these were left empty (for example, privacy, or the token not being resolvable at that layer) that we should consider? 3. If the change is welcome, should it go to Bugzilla first or straight to a GitLab merge request against master? We would be glad to test a patch against 4.23 with Windows and macOS clients and report results. Thank you, Derek Perez Limbic Systems From pc at manguebit.org Wed Oct 14 23:06:19 2026 From: pc at manguebit.org (Paulo Alcantara) Date: Wed, 14 Oct 2026 20:06:19 -0300 Subject: [PATCH 1/1] cifs: fix buffer overflow in SMB2_tcon In-Reply-To: <0f3d431d65a5f46c0487293d127e748775ca85b8.1791865501.git.rakukuip@gmail.com> References: <0f3d431d65a5f46c0487293d127e748775ca85b8.1791865501.git.rakukuip@gmail.com> Message-ID: Ren Wei writes: > From: Luxiao Xu > > In SMB2_tcon(), unc_path is allocated with a fixed buffer size of > MAX_SHARENAME_LENGTH * 2 bytes (672 bytes). However, the user-controlled > UNC path (tree) length is passed to cifs_strtoUTF16(), which assumes the > destination buffer is large enough and writes strlen(tree) + 1 UTF-16 code > units. > > Commit 62a1a439e0fd ("cifs: clean up handling of unc= option") removed the > length check on UNC mount options, allowing mount paths to exceed > MAX_SHARENAME_LENGTH while retaining the fixed-size buffer allocation in > SMB2_tcon(). Consequently, an oversized UNC string causes cifs_strtoUTF16() > to write past the end of the heap object, resulting in a slab-out-of-bounds > write. > > Fix this by dynamically allocating unc_path based on the length of tree so > that it safely accommodates all UTF-16 code units. I don't think we should allow SMB2_tcon() with a @tree more than MAX_TREE_SIZE characters. With your fix, we might end up with a truncated tree name set in cifs_tcon::tree_name. It will work in the first tree connect, but if the client needs to issue another tree connect for some reason (e.g. reconnect/failover), then it will fail. Some places rely on the tree name not having more than MAX_TREE_SIZE characters. I'd rather return -EINVAL along with an error message in SMB2_tcon() if @tree exceeds that length. >From another patch, we could also add an early MAX_TREE_SIZE check against smb3_fs_context::UNC in smb3_parse_devname(). BTW, mount.cifs(8) will not allow an UNC path that has more than 1024 characters. What do you think?