From: Mahanta Jambigi <mjambigi@linux.ibm.com>
To: sashiko-reviews@lists.linux.dev
Cc: linux-s390@vger.kernel.org, linux-rdma@vger.kernel.org,
Heiko Carstens <hca@linux.ibm.com>,
Vasily Gorbik <gor@linux.ibm.com>,
Alexander Gordeev <agordeev@linux.ibm.com>,
Christian Borntraeger <borntraeger@linux.ibm.com>
Subject: Re: [PATCH net v12] net/smc: fix lgr/lnk lifetime vs diag reader race
Date: Tue, 20 Oct 2026 19:25:45 +0530 [thread overview]
Message-ID: <b54bb916-7e0e-4014-b0bb-e61ac0926b32@linux.ibm.com> (raw)
In-Reply-To: <sashiko-outbox-178883@kernel.org>
On 20/10/26 4:40 pm, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] net/smc: race in smc_lgr_schedule_free_work() can arm free_work before kfree
> - [High] net/smc: missing release barrier on client SMC_INIT to SMC_CLOSED transition
> --
>
> commit ac5d5191510569377b3ad3881fe61cfb7d941a9d
> Author: Mahanta Jambigi <mjambigi@linux.ibm.com>
>
> net/smc: fix lgr/lnk lifetime vs diag reader race
>
> This commit addresses several races between SMC socket/link group teardown
> and concurrent sock_diag readers by adjusting sk_state ordering, unhashing
> sockets earlier in teardown paths, skipping extended diag attributes in
> SMC_INIT/SMC_CLOSED, and unconditionally unregistering connections in
> smc_conn_free().
>
>> diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
>> index 9974149659c2f..9d8165d1b40c5 100644
>> --- a/net/smc/smc_core.c
>> +++ b/net/smc/smc_core.c
>> @@ -84,7 +84,7 @@ static void smc_lgr_schedule_free_work(struct smc_link_group *lgr)
>> * creation. For client use a somewhat higher removal delay time,
>> * otherwise there is a risk of out-of-sync link groups.
>> */
>> - if (!lgr->freeing) {
>> + if (!lgr->freeing && !lgr->terminating) {
>> mod_delayed_work(system_percpu_wq, &lgr->free_work,
>> (!lgr->is_smcd && lgr->role == SMC_CLNT) ?
>> SMC_LGR_FREE_DELAY_CLNT :
>> @@ -1279,10 +1279,9 @@ void smc_conn_free(struct smc_connection *conn)
>> if (current_work() != &conn->abort_work)
>> cancel_work_sync(&conn->abort_work);
>> }
>> - if (!list_empty(&lgr->list)) {
>> + if (!list_empty(&lgr->list))
>> smc_buf_unuse(conn, lgr); /* allow buffer reuse */
>> - smc_lgr_unregister_conn(conn);
>> - }
>> + smc_lgr_unregister_conn(conn);
>>
>> if (!lgr->conns_num)
>> smc_lgr_schedule_free_work(lgr);
>
> [Severity: Critical]
> Can smc_lgr_schedule_free_work() still race with __smc_lgr_terminate() and
> re-arm lgr->free_work right before lgr is freed?
>
> In smc_lgr_cleanup_early(), the link group is unlinked from lgr->list while
> both lgr->freeing and lgr->terminating are still 0, and then
> __smc_lgr_terminate() cancels free_work before setting lgr->terminating:
>
> net/smc/smc_core.c:__smc_lgr_terminate() {
> if (lgr->terminating)
> return; /* lgr already terminating */
> /* cancel free_work sync, will terminate when lgr->freeing is set */
> cancel_delayed_work(&lgr->free_work);
> lgr->terminating = 1;
> ...
> }
>
> Because smc_conn_free() now calls smc_lgr_unregister_conn(conn)
> unconditionally and invokes smc_lgr_schedule_free_work(lgr) outside
> lgr->conns_lock, can the following interleaving occur when another
> connection on the same link group is concurrently freed?
>
> CPU0 (smc_conn_free) CPU1 (smc_lgr_cleanup_early)
> -------------------- ----------------------------
> smc_lgr_unregister_conn(conn)
> lgr->conns_num becomes 0
> smc_lgr_schedule_free_work(lgr)
> sees !lgr->freeing &&
> !lgr->terminating
> __smc_lgr_terminate(lgr, true)
> cancel_delayed_work(&lgr->free_work)
> lgr->terminating = 1
> ...
> smc_lgr_free(lgr)
> smc_lgr_put(lgr)
> mod_delayed_work(..., &lgr->free_work, ...)
> smc_lgr_put(lgr)
> __smc_lgr_free(lgr)
> kfree(lgr)
>
> If that happens, wouldn't lgr be freed via kfree(lgr) while lgr->free_work
> remains queued in system_percpu_wq?
The interleaving is theoretically possible but *pre-existing* and not
widened by this patch. It requires a first-contact connection A to fail
and take the smc_lgr_cleanup_early() path, a second connection B to have
joined the same lgr after smc_server_lgr_pending was dropped, and B's
smc_conn_free() to bring conns_num to 0 within a few instructions of A's
__smc_lgr_terminate() cancelling free_work, with A's teardown completing
before B's final smc_lgr_put().
The unlocked "!lgr->freeing" check followed by mod_delayed_work() in
smc_lgr_schedule_free_work() has the same non-atomic window against
smc_lgr_terminate_sched() and __smc_lgr_terminate() in the baseline. The
"!lgr->terminating" check added here only avoids re-arming once
terminating is already visible, so it keeps the window no larger than
before. Closing it fully requires reworking how free_work scheduling is
serialized against lgr termination (check and arm under lgr_lock, or
cancelling free_work in __smc_lgr_free()). That is a separate lgr
lifetime fix, out of scope for this diag reader race patch.
>
> [ ... ]
>
>> diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
>> index bf0beaa23bdb6..36a3b8f31f28d 100644
>> --- a/net/smc/smc_diag.c
>> +++ b/net/smc/smc_diag.c
>> @@ -87,10 +87,11 @@ static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
>>
>> r = nlmsg_data(nlh);
>> smc_diag_msg_common_fill(r, sk);
>> - r->diag_state = sk->sk_state;
>> + r->diag_state = smp_load_acquire(&sk->sk_state);
>> if (smc->use_fallback)
>> r->diag_mode = SMC_DIAG_MODE_FALLBACK_TCP;
>> - else if (smc_conn_lgr_valid(&smc->conn) && smc->conn.lgr->is_smcd)
>> + else if (r->diag_state != SMC_INIT &&
>> + smc_conn_lgr_valid(&smc->conn) && smc->conn.lgr->is_smcd)
>> r->diag_mode = SMC_DIAG_MODE_SMCD;
>> else
>> r->diag_mode = SMC_DIAG_MODE_SMCR;
>
> [Severity: High]
> Should this condition also exclude r->diag_state == SMC_CLOSED, or should
> smc_close_active() use smp_store_release() when transitioning from SMC_INIT
> to SMC_CLOSED?
>
> On the client side, if smc_connect_rdma() or smc_connect_ism() fails after
> smc_conn_create() and jumps to connect_abort, smc_conn_abort() calls
> smc_conn_free() and smc_lgr_cleanup_early(), which clears
> conn->alert_token_local and frees conn->lgr while leaving conn->lgr non-NULL.
>
> If fallback is not possible in smc_connect_decline_fallback() (or fails in
> smc_connect_fallback()), smc->use_fallback remains false and sk->sk_state
> remains SMC_INIT.
>
> When the socket is then closed via smc_release() -> __smc_release() ->
> smc_close_active(), sk->sk_state is set to SMC_CLOSED with a plain store
> while the socket is still hashed:
>
> net/smc/smc_close.c:smc_close_active() {
> ...
> switch (sk->sk_state) {
> case SMC_INIT:
> sk->sk_state = SMC_CLOSED;
> break;
> ...
> }
>
> On weakly ordered CPUs, if a concurrent reader in __smc_diag_dump() observes
> r->diag_state == SMC_CLOSED from that plain store, there is no release
> barrier ordering the earlier conn->alert_token_local = 0 store before the
> sk_state transition, and the diag_mode check here only filters out SMC_INIT
> (unlike the SMC_INIT || SMC_CLOSED check further down). Could
> __smc_diag_dump() then see a stale non-zero alert_token_local and
> dereference the freed smc->conn.lgr->is_smcd?
On this path alert_token_local is cleared in the connect syscall, under
conns_lock with the socket lock held. The SMC_INIT -> SMC_CLOSED store
only happens later in close() -> smc_close_active() under lock_sock().
The lock_sock()/release_sock() handoff and the conns_lock write_unlock
between the two order the clear before the sk_state store, so a reader
that sees SMC_CLOSED *cannot* see a stale non-zero alert_token_local.
prev parent reply other threads:[~2026-10-20 13:55 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 11:10 [PATCH net v12] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
2026-10-19 11:13 ` netdev-bot+sinfo
2026-10-19 14:03 ` Mahanta Jambigi
2026-10-20 11:10 ` sashiko-bot
2026-10-20 13:53 ` Mahanta Jambigi
2026-10-20 13:55 ` Mahanta Jambigi [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=b54bb916-7e0e-4014-b0bb-e61ac0926b32@linux.ibm.com \
--to=mjambigi@linux.ibm.com \
--cc=agordeev@linux.ibm.com \
--cc=borntraeger@linux.ibm.com \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=linux-rdma@vger.kernel.org \
--cc=linux-s390@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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