mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: nicoyip.dev@gmail.com
Cc: alibuda@linux.alibaba.com, dust.li@linux.alibaba.com,
	sidraya@linux.ibm.com, mjambigi@linux.ibm.com,
	tonylu@linux.alibaba.com, guwen@linux.alibaba.com,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, horms@kernel.org, kgraul@linux.ibm.com,
	ubraun@linux.ibm.com, linux-rdma@vger.kernel.org,
	linux-s390@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH net] net/smc: serialize link group free work scheduling
Date: Wed, 30 Sep 2026 00:45:25 +0000	[thread overview]
Message-ID: <179072912501.434549.7831981029772644673@kernel.org> (raw)
In-Reply-To: <20260927074520.3694663-1-nicoyip.dev@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The fix is incomplete.
- [Medium] The patch treats lgr_lock as the lock for lgr->freeing (the
  new comment reads 'protects lgr->freeing').

Pre-existing issues:
- [High] Separately from the pending-timer rearm this patch fixes, a
  free_work callback that has already started can race with lgr teardown…

--

net/smc: serialize link group free work scheduling

This patch takes the link group list lock in smc_lgr_schedule_free_work()
around the lgr->freeing check and the mod_delayed_work() call. The goal is
to stop a concurrent smc_lgr_free_work() from setting freeing and cancelling
the work between the check and the rearm, which would leave a timer pending
on a freed link group.

> diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
> index 9974149659c2f..8feb486ada562 100644
> --- a/net/smc/smc_core.c
> +++ b/net/smc/smc_core.c
> @@ -80,6 +80,10 @@ static void smc_ibdev_cnt_dec(struct smc_link *lnk)
>  
>  static void smc_lgr_schedule_free_work(struct smc_link_group *lgr)
>  {
> +	spinlock_t *lgr_lock; /* protects lgr->freeing */

[Severity: Medium]
Is lgr_lock enough to protect lgr->freeing? freeing is a 1-bit bitfield,
and it shares a u8 with sync_err and terminating:

net/smc/smc_core.h:struct smc_link_group {
	...
	u8			sync_err : 1;	/* lgr no longer fits to peer */
	u8			terminating : 1;/* lgr is terminating */
	u8			freeing : 1;	/* lgr is being freed */
	...
}

Both neighbouring bits are written without lgr_lock. One example is
smc_clc_wait_msg():

	smc->conn.lgr->sync_err = 1;
	smc_lgr_terminate_sched(smc->conn.lgr);

Another is __smc_lgr_terminate():

	lgr->terminating = 1;

Each of these stores is a read-modify-write of the whole byte.
smc_smcd_terminate() and smc_lgr_free_work() set freeing = 1 under
lgr_lock. Can a concurrent sync_err or terminating store write back a stale
byte and lose that freeing = 1?

In the sync_err case, smc_lgr_terminate_sched() then returns early because
the lgr is already unlinked, so freeing is never set again.

Consider a later smc_conn_free() that passed its unlocked list_empty()
check before the unlink. Once __smc_lgr_terminate() has cancelled
free_work, that call could reach smc_lgr_schedule_free_work() with
conns_num == 0. It would read freeing == 0 under the new lock and rearm the
timer on an lgr that is about to be freed.

Would it work better to give freeing its own storage (for example a bool),
or to make every writer of that byte hold lgr_lock?

> +
> +	smc_lgr_list_head(lgr, &lgr_lock);
> +	spin_lock_bh(lgr_lock);
>  	/* client link group creation always follows the server link group
>  	 * creation. For client use a somewhat higher removal delay time,
>  	 * otherwise there is a risk of out-of-sync link groups.
> @@ -90,6 +94,7 @@ static void smc_lgr_schedule_free_work(struct smc_link_group *lgr)
>  						SMC_LGR_FREE_DELAY_CLNT :
>  						SMC_LGR_FREE_DELAY_SERV);
>  	}
> +	spin_unlock_bh(lgr_lock);
>  }

[Severity: High]
Does this fully close the rearm race? The commit message says a rearm
"either precedes the freeing transition and is caught by the subsequent
cancellation, or sees freeing set and is skipped". That only holds if every
teardown path sets lgr->freeing under lgr_lock before it cancels free_work.

smc_lgr_cleanup_early() unlinks the lgr under lgr_lock but never sets
freeing:

net/smc/smc_core.c:smc_lgr_cleanup_early() {
	...
	spin_lock_bh(lgr_lock);
	/* do not use this link group for new connections */
	if (!list_empty(&lgr->list))
		list_del_init(&lgr->list);
	spin_unlock_bh(lgr_lock);
	__smc_lgr_terminate(lgr, true);
}

__smc_lgr_terminate() then cancels only once, outside the lock. It sets
terminating, not freeing:

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;
	...
	smc_lgr_cleanup(lgr);
	smc_lgr_free(lgr);
}

On an SMC-D server, smc_listen_work() drops smc_server_lgr_pending after
it sends the ACCEPT. A second connection (conn2) from the same peer can
then join the first-contact lgr.

If the first connection then fails (no CONFIRM, or a DECLINE), teardown
goes through:

smc_listen_work()->smc_listen_decline()->smc_conn_abort()->
    smc_lgr_cleanup_early()->__smc_lgr_terminate()

Meanwhile conn2 can be freed on another CPU:

CPU0 (conn1 abort)                CPU1 (smc_conn_free(conn2))
                                  !list_empty(&lgr->list) is true
                                  smc_lgr_unregister_conn(conn2)
smc_lgr_cleanup_early()
  list_del_init(&lgr->list)
  __smc_lgr_terminate()
    cancel_delayed_work()
    lgr->terminating = 1
                                  lgr->conns_num == 0
                                  smc_lgr_schedule_free_work()
                                    spin_lock_bh(lgr_lock)
                                    lgr->freeing == 0
                                    mod_delayed_work()
    smc_lgr_free()
                                  smc_lgr_put() drops last reference

This would leave free_work's timer pending on a kfree'd lgr, which is the
same __run_timers use-after-free as in the KASAN report.

Should smc_lgr_cleanup_early() set lgr->freeing under lgr_lock? Or should
smc_lgr_schedule_free_work() also skip the rearm when the lgr is unlinked
or terminating?

[Severity: High]
This is a pre-existing issue and was not introduced by this patch. Can a
smc_lgr_free_work() callback that has already started running race with
lgr teardown?

smc_lgr_free_work() holds no reference on the lgr and dereferences it
straight away:

net/smc/smc_core.c:smc_lgr_free_work() {
	...
	smc_lgr_list_head(lgr, &lgr_lock);
	spin_lock_bh(lgr_lock);
	if (lgr->freeing) {
	...
}

Suppose the idle timer fires and the worker starts, but is delayed before
this point (by preemption, or on PREEMPT_RT). A teardown can then set
freeing and reach __smc_lgr_terminate(). Examples are smc_smcd_terminate()
from an ISM peer shutdown event in smcd_handle_sw_event(),
smc_lgr_terminate_sched(), and smc_smcd_terminate_all() or
smc_smcr_terminate_all(). The only cancellation there is:

	cancel_delayed_work(&lgr->free_work);

That call neither cancels nor waits for a callback that is already
running.

With no connections left, smc_lgr_free() destroys lgr->tx_wq. free_work is
on system_percpu_wq, so this does not drain it. smc_lgr_put() then calls
__smc_lgr_free(), which frees the lgr.

When the worker resumes, it takes spin_lock_bh() on a lock pointer loaded
from freed memory. If the stale freeing bit reads 0, it also calls
list_del_init() and smc_lgr_free() a second time.

Should teardown use cancel_delayed_work_sync() where the context allows,
or should the queued work hold its own lgr reference?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927074520.3694663-1-nicoyip.dev%40gmail.com

      reply	other threads:[~2026-09-30  0:45 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-27  7:45 Chengfeng Ye
2026-09-30  0:45 ` netdev-bot+sashiko [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=179072912501.434549.7831981029772644673@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=alibuda@linux.alibaba.com \
    --cc=davem@davemloft.net \
    --cc=dust.li@linux.alibaba.com \
    --cc=edumazet@google.com \
    --cc=guwen@linux.alibaba.com \
    --cc=horms@kernel.org \
    --cc=kgraul@linux.ibm.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    --cc=linux-s390@vger.kernel.org \
    --cc=mjambigi@linux.ibm.com \
    --cc=netdev@vger.kernel.org \
    --cc=nicoyip.dev@gmail.com \
    --cc=pabeni@redhat.com \
    --cc=sidraya@linux.ibm.com \
    --cc=stable@vger.kernel.org \
    --cc=tonylu@linux.alibaba.com \
    --cc=ubraun@linux.ibm.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

all inboxes | Powered by JetHome®