From: Greg KH <greg@kroah.com>
To: Sasha Levin <sashal@kernel.org>
Cc: stable@vger.kernel.org, DaeMyung Kang <charsyam@gmail.com>,
Namjae Jeon <linkinjeon@kernel.org>,
Steve French <stfrench@microsoft.com>
Subject: Re: [PATCH 6.6.y] ksmbd: rewrite stop_sessions() with restartable iteration
Date: Fri, 15 May 2026 12:31:37 +0200 [thread overview]
Message-ID: <2026051531-decathlon-trance-8b3c@gregkh> (raw)
In-Reply-To: <20260510200017.599429-1-sashal@kernel.org>
On Sun, May 10, 2026 at 04:00:16PM -0400, Sasha Levin wrote:
> From: DaeMyung Kang <charsyam@gmail.com>
>
> [ Upstream commit c444139cb747bf6de1922b39900fdf02281490f4 ]
>
> stop_sessions() walks conn_list with hash_for_each() and, for every
> entry, drops conn_list_lock across the transport ->shutdown() call
> before re-acquiring the read lock to continue the loop. The hash
> walk relies on cross-iteration state (the current bucket and the
> hlist position), which is not preserved across unlock/relock: if
> another thread performs a list mutation during the unlocked window,
> the ongoing iteration becomes unreliable and can re-visit
> connections that have already been handled or skip connections that
> have not. The outer `if (!hash_empty(conn_list)) goto again;` retry
> masks the symptom in the common case but does not address the
> unsafe iteration itself.
>
> Reframe the loop so it never relies on iterator state across
> unlock/relock. Under conn_list_lock held for read, pick the first
> connection whose ->shutdown() has not yet been issued by this path,
> pin it by taking an extra reference, record that fact on the
> connection and mark it EXITING while still inside the locked walk,
> then drop the lock. Then call ->shutdown() outside the lock, drop
> the pin (freeing the connection if the handler already released its
> reference), and restart from the top.
>
> Use a new per-connection flag, conn->stop_called, as the "shutdown
> issued from stop_sessions()" marker rather than reusing the status
> state. ksmbd_conn_set_exiting() is also invoked by
> ksmbd_sessions_deregister() on sibling channels of a multichannel
> session without issuing a transport shutdown, so treating
> KSMBD_SESS_EXITING as "already handled here" would skip connections
> that still need shutdown() to wake their handler out of recv(),
> leaving the outer retry waiting indefinitely for the hash to drain.
> stop_sessions() is serialised by init_lock in
> ksmbd_conn_transport_destroy(), so writing stop_called under the
> read lock has no other writer.
>
> Set EXITING inside the locked walk so the selection, the stop_called
> marker, and the status transition all happen together, and guard
> against regressing a connection that has already advanced to
> KSMBD_SESS_RELEASING on its own (for example, if the handler exited
> its receive loop for an unrelated reason between teardown steps).
>
> When the pin drop is the last put, release the transport and pair
> ida_destroy(&target->async_ida) with the ida_init() done in
> ksmbd_conn_alloc(), so stop_sessions() retiring a connection on its
> own does not leak the xarray backing of the embedded async_ida.
>
> The outer retry with msleep() is kept to wait for handler threads to
> reach ksmbd_conn_free() and drain the hash.
>
> Observed with an instrumented build that logs one line per visit and
> widens the unlocked window before ->shutdown() by 200 ms, under
> five concurrent cifs mounts (nosharesock, one connection each):
>
> * Current code: the same connection address is revisited many
> times during a single stop_sessions() call and ->shutdown() is
> invoked well beyond the number of live connections before the
> hash finally drains.
>
> * Rewritten code: each live connection produces exactly one
> ->shutdown() call; the function returns as soon as the hash is
> empty.
>
> Functional teardown via `ksmbd.control --shutdown` with the same
> five mounts completes cleanly on the rewritten path.
>
> Performance is observably unchanged. Tearing down N concurrent
> nosharesock cifs connections with `ksmbd.control --shutdown` +
> `rmmod ksmbd` takes essentially the same wall time before and after
> the rewrite:
>
> N before after
> 10 4.93s 5.34s
> 30 7.34s 7.03s
> 50 7.31s 7.01s (3-run avg: 7.04s vs 7.25s)
> 100 6.98s 6.78s
> 200 6.77s 6.89s
>
> and the number of ->shutdown() calls equals the number of live
> connections on both paths when the race is not widened. The
> teardown is dominated by the msleep(100)-based outer retry waiting
> for handler threads to run ksmbd_conn_free(), not by the iteration
> itself; the restartable loop's worst-case O(N^2) visit cost is in
> the microseconds even at N=200 and sits far below the msleep(100)
> granularity.
>
> Applied alone on top of ksmbd-for-next-next, this patch does not
> introduce a new leak site. Under the same reproducer (10x
> concurrent-holders + ss -K + ksmbd.control --shutdown + rmmod), the
> tree still shows the pre-existing per-connection transport leak
> count that arises when the last refcount drop lands in one of
> ksmbd_conn_r_count_dec(), __free_opinfo() or session_fd_check() -
> all of which end with a bare kfree() today. kmemleak backtraces
> for the unreferenced objects point into the TCP accept path
> (sk_clone -> inet_csk_clone_lock, sock_alloc_inode) and none
> involve stop_sessions(). Plugging those bare-kfree sites is the
> responsibility of the follow-up patch.
>
> Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
> Cc: stable@vger.kernel.org
> Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
> Acked-by: Namjae Jeon <linkinjeon@kernel.org>
> Signed-off-by: Steve French <stfrench@microsoft.com>
> [ kept list_for_each_entry iteration and schedule_timeout_interruptible(HZ/10) ]
> Signed-off-by: Sasha Levin <sashal@kernel.org>
> ---
> fs/smb/server/connection.c | 46 +++++++++++++++++++++++++++++++-------
> fs/smb/server/connection.h | 1 +
> 2 files changed, 39 insertions(+), 8 deletions(-)
>
> diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
> index 907ddfc2c2c1d..51e7e7042fb05 100644
> --- a/fs/smb/server/connection.c
> +++ b/fs/smb/server/connection.c
> @@ -478,23 +478,53 @@ int ksmbd_conn_transport_init(void)
>
> static void stop_sessions(void)
> {
> - struct ksmbd_conn *conn;
> + struct ksmbd_conn *conn, *target;
> struct ksmbd_transport *t;
> + bool any;
>
> + /*
> + * Serialised via init_lock; no concurrent stop_sessions() can
> + * touch conn->stop_called, so writing it under the read lock is
> + * safe.
> + */
> again:
> + target = NULL;
> + any = false;
> down_read(&conn_list_lock);
> list_for_each_entry(conn, &conn_list, conns_list) {
> - t = conn->transport;
> - ksmbd_conn_set_exiting(conn);
> - if (t->ops->shutdown) {
> - up_read(&conn_list_lock);
> + any = true;
> + if (conn->stop_called)
> + continue;
> + atomic_inc(&conn->refcnt);
> + conn->stop_called = true;
> + /*
> + * Mark the connection EXITING while still holding the
> + * read lock so the selection and the status transition
> + * happen together. Do not regress a connection that has
> + * already advanced to RELEASING on its own (e.g. the
> + * handler exited its receive loop for an unrelated
> + * reason).
> + */
> + if (READ_ONCE(conn->status) != KSMBD_SESS_RELEASING)
> + ksmbd_conn_set_exiting(conn);
> + target = conn;
> + break;
> + }
> + up_read(&conn_list_lock);
> +
> + if (target) {
> + t = target->transport;
> + if (t->ops->shutdown)
> t->ops->shutdown(t);
> - down_read(&conn_list_lock);
> + if (atomic_dec_and_test(&target->refcnt)) {
> + ida_destroy(&target->async_ida);
> + t->ops->free_transport(t);
> + kfree(target);
> }
> + goto again;
> }
> - up_read(&conn_list_lock);
>
> - if (!list_empty(&conn_list)) {
> + if (any) {
> schedule_timeout_interruptible(HZ / 10); /* 100ms */
> goto again;
> }
> diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
> index 45421269ddd88..e196f723358ef 100644
> --- a/fs/smb/server/connection.h
> +++ b/fs/smb/server/connection.h
> @@ -46,6 +46,7 @@ struct ksmbd_conn {
> struct mutex srv_mutex;
> int status;
> unsigned int cli_cap;
> + bool stop_called;
> union {
> __be32 inet_addr;
> #if IS_ENABLED(CONFIG_IPV6)
> --
> 2.53.0
>
>
Does not apply :(
prev parent reply other threads:[~2026-05-15 10:31 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-07 10:25 FAILED: patch "[PATCH] ksmbd: rewrite stop_sessions() with restartable iteration" failed to apply to 6.6-stable tree gregkh
2026-05-10 20:00 ` [PATCH 6.6.y] ksmbd: rewrite stop_sessions() with restartable iteration Sasha Levin
2026-05-15 10:31 ` Greg KH [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2026051531-decathlon-trance-8b3c@gregkh \
--to=greg@kroah.com \
--cc=charsyam@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=sashal@kernel.org \
--cc=stable@vger.kernel.org \
--cc=stfrench@microsoft.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.