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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox