From: netdev-bot+sashiko@kernel.org
To: mjambigi@linux.ibm.com
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, alibuda@linux.alibaba.com,
dust.li@linux.alibaba.com, sidraya@linux.ibm.com,
hidayath@linux.ibm.com, pasic@linux.ibm.com, horms@kernel.org,
tonylu@linux.alibaba.com, guwen@linux.alibaba.com,
stable@vger.kernel.org, netdev@vger.kernel.org,
linux-s390@vger.kernel.org, linux-rdma@vger.kernel.org
Subject: Re: [PATCH net v14] net/smc: fix lgr/lnk lifetime vs diag reader race
Date: Sun, 01 Nov 2026 07:15:51 +0000 [thread overview]
Message-ID: <179351735189.3131.9219259330792018704@kernel.org> (raw)
In-Reply-To: <20261028070210.1685136-1-mjambigi@linux.ibm.com>
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 (2) · Low: 1
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] After this patch, __smc_diag_dump() (net/smc/smc_diag.c)
reports SMC_DIAG_MODE_SMCR for every non-fallback socket in SMC_INIT…
- [Low] The commit message's 'Fourth' item says the
smp_store_release(&newsmcsk->sk_state, SMC_CLOSED) in…
Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but
smc_conn_free() (net/smc/smc_core.c:1281-1282) calls…
- [Medium] This problem wasn't introduced by this patch, but
smc_diag_handler_dump() (net/smc/smc_diag.c) never checks that the…
--
net/smc: fix lgr/lnk lifetime vs diag reader race
This patch tries to close races between the SMC sock_diag dump and the
teardown of conn/lgr/lnk state. __smc_diag_dump() now skips the extension
attributes and the lgr based diag_mode test for sockets in SMC_INIT,
SMC_CLOSED or fallback, and several teardown paths unhash the socket
before smc_conn_free() or smcd_buf_detach(). It also adds acquire/release
ordering on sk_state and copies the cached link->ibname.
> diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> index d10244af5bd29..ec5980add7a9d 100644
> --- a/net/smc/af_smc.c
> +++ b/net/smc/af_smc.c
[ ... ]
> @@ -1971,7 +1971,7 @@ static void smc_listen_out_err(struct smc_sock *new_smc)
> this_cpu_inc(net->smc.smc_stats->srv_hshake_err_cnt);
> if (newsmcsk->sk_state == SMC_INIT)
> sock_put(&new_smc->sk); /* passive closing */
> - newsmcsk->sk_state = SMC_CLOSED;
> + smp_store_release(&newsmcsk->sk_state, SMC_CLOSED);
[Severity: Low]
The commit message says:
Fourth, smc_listen_out_err() sets sk_state = SMC_CLOSED with a plain
store after smc_conn_abort() -> smc_conn_free() has already cleared
alert_token_local under write_lock_bh(). On weakly ordered CPUs a
concurrent diag reader using smp_load_acquire() on sk_state can observe
SMC_CLOSED while the preceding store to alert_token_local has not yet
become visible, causing it to dereference an lgr whose reference was
already dropped.
Is this still accurate with the current __smc_diag_dump()?
A reader that sees SMC_CLOSED now skips the conn->lgr dereference in the
diag_mode test, and the new goto out skips the extension blocks.
Before this store the state is SMC_INIT. smc_listen_work() holds the
socket lock until smc_listen_out(), so close_work can't change it in
between, and SMC_INIT is skipped the same way.
So whichever value the reader sees, it never reads alert_token_local or
conn->lgr, and the release ordering doesn't seem to protect anything the
reader relies on. The commit message already says this for the
stale-token case ("the SMC_CLOSED check in __smc_diag_dump() keeps the
reader from dereferencing conn->lgr").
This reasoning seems to date from v9, before v14 added SMC_CLOSED to the
diag_mode test. Could the fourth item be described as hardening, or
dropped, so stable backporters don't treat the barrier as a required
standalone fix?
>
> smc_listen_out(new_smc);
> }
> diff --git a/net/smc/smc_close.c b/net/smc/smc_close.c
> index a8d4b4b65f244..e26586114f802 100644
> --- a/net/smc/smc_close.c
> +++ b/net/smc/smc_close.c
[ ... ]
> @@ -437,6 +439,7 @@ static void smc_close_passive_work(struct work_struct *work)
> sk->sk_state_change(sk);
> if ((sk->sk_state == SMC_CLOSED) &&
> (sock_flag(sk, SOCK_DEAD) || !sk->sk_socket)) {
> + sk->sk_prot->unhash(sk);
> smc_conn_free(conn);
[Severity: Medium]
This is a pre-existing issue and wasn't introduced by this patch. Does
smc_conn_free() leak the socket reference held by abort_work when it
cancels a pending abort_work?
smc_conn_free() in net/smc/smc_core.c does:
} else {
smc_cdc_wait_pend_tx_wr(conn);
if (current_work() != &conn->abort_work)
cancel_work_sync(&conn->abort_work);
}
and ignores the return value. The only code that schedules abort_work is
smc_cdc_msg_validate(), and it hands a reference to the work:
sock_hold(&smc->sk); /* sock_put in abort_work */
if (!queue_work(smc_close_wq, &conn->abort_work))
sock_put(&smc->sk);
smc_conn_abort_work() drops that reference when it runs. Suppose
abort_work is still pending when an SMC-R connection reaches
smc_conn_free() from outside abort_work, for example via __smc_release().
Then cancel_work_sync() returns true, nobody drops the reference, and the
smc_sock is never freed.
smc_close_cancel_work() handles close_work the same way with:
if (cancel_work_sync(&smc->conn.close_work))
sock_put(sk);
Should smc_conn_free() do the same for abort_work?
[ ... ]
> diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
> index bf0beaa23bdb6..7b4656d6560a6 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 && r->diag_state != SMC_CLOSED &&
> + 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: Medium]
With this change every non-fallback socket in SMC_INIT or SMC_CLOSED is
reported as SMC_DIAG_MODE_SMCR. That includes SMC-D connections, which
used to report SMC_DIAG_MODE_SMCD.
include/uapi/linux/smc_diag.h defines diag_mode as:
/* Mode of a connection */
enum {
SMC_DIAG_MODE_SMCR,
SMC_DIAG_MODE_FALLBACK_TCP,
SMC_DIAG_MODE_SMCD,
};
It has no "unknown" value. Is it intended that an SMC-D connection is
reported as SMC-R here?
Live SMC-D sockets can stay hashed in SMC_CLOSED with a valid lgr and
alert_token_local. smc_close_passive_work() or
smc_close_passive_abort_received() can move a socket to SMC_CLOSED while
the fd is still open. smc_close_passive_work() only unhashes and frees
when:
if ((sk->sk_state == SMC_CLOSED) &&
(sock_flag(sk, SOCK_DEAD) || !sk->sk_socket)) {
SMC-D sockets in SMC_INIT after smc_conn_create() are affected the same
way. A tool that filters by mode (smcss -D / -R style) would put these
sockets in the wrong group.
Could the race be avoided without misreporting? For example, is_smcd
could be cached in the smc_sock, or SMC-D could be inferred from
conn.lgr != NULL && conn.lnk == NULL, which only reads pointers and
doesn't dereference the lgr.
> user_ns = sk_user_ns(NETLINK_CB(cb->skb).sk);
> if (smc_diag_msg_attrs_fill(sk, skb, r, user_ns))
> goto errout;
> @@ -103,6 +104,10 @@ static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
> if (nla_put(skb, SMC_DIAG_FALLBACK, sizeof(fallback), &fallback) < 0)
> goto errout;
>
> + if (r->diag_state == SMC_INIT || r->diag_state == SMC_CLOSED ||
> + smc->use_fallback)
> + goto out;
> +
[Severity: High]
Can a socket whose connection was already released by smc_conn_abort()
still move into a state this guard doesn't cover?
The guard only checks the sk_state snapshot. Take a client handshake
that fails with a negative rc after smc_conn_create():
smc_connect_rdma()
connect_abort:
smc_conn_abort()
smc_conn_free() /* sets conn->freed, drops lgr/lnk refs */
smc_connect_decline_fallback() returns early without changing the state,
and smc_connect_work() only does:
rc = __smc_connect(smc);
if (rc < 0)
smc->sk.sk_err = -rc;
So the socket stays hashed in SMC_INIT with use_fallback == false.
Suppose smc_lgr_terminate_sched() had already emptied lgr->list, which
is the stale-token case the changelog mentions. Then smc_conn_free()
skips the unregister:
if (!list_empty(&lgr->list)) {
smc_buf_unuse(conn, lgr); /* allow buffer reuse */
smc_lgr_unregister_conn(conn);
}
alert_token_local stays non-zero and the conn stays in lgr->conns_all.
smc_cdc_rx_handler() doesn't check conn->freed:
conn = smc_lgr_find_conn(ntohl(cdc->token), lgr);
if (!conn || conn->out_of_sync) {
So a close or peer-abort CDC can still queue close_work. Once the
handshake thread drops the socket lock, smc_close_passive_work() does:
case SMC_INIT:
sk->sk_state = SMC_APPCLOSEWAIT1;
break;
Or smc_close_passive_abort_received() moves SMC_INIT to
SMC_PROCESSABORT. Both are plain stores, and neither path reaches the
new unhash plus smc_conn_free() branch.
A diag reader under read_lock(&h->lock) would then see APPCLOSEWAIT1 or
PROCESSABORT and pass both new checks. It would find
smc_conn_lgr_valid() true, because conn->lgr is never cleared. It then
dereferences smc->conn.lgr->is_smcd, conn->sndbuf_desc, conn->rmb_desc
and lgr->list.
Meanwhile terminate_work runs
__smc_lgr_terminate()->smc_conn_kill()->smc_close_active_abort(). Its
ACTIVE/APPCLOSEWAIT branch goes to SMC_CLOSED without unhashing:
case SMC_ACTIVE:
case SMC_APPCLOSEWAIT1:
case SMC_APPCLOSEWAIT2:
sk->sk_state = SMC_PEERABORTWAIT;
smc_close_cancel_work(smc);
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* (postponed) passive closing */
So the hash write_lock is never taken before smc_lgr_free() drops the
last lgr reference. Wouldn't the reader then be dereferencing a freed
smc_link_group?
There is also a weaker variant, where the token is cleared normally and
the lgr is freed synchronously via smc_lgr_cleanup_early(). Safety then
depends on the token clear being visible to a reader that acquire-loads
an sk_state written with a plain store. This is probably ordered in
practice through lock_sock()/release_sock(), but the patch doesn't
establish it.
Separately, after a failed handshake the same SMC_INIT to
SMC_APPCLOSEWAIT1 transition later runs the "postponed passive closing"
sock_put() in smc_close_active() or smc_close_active_abort(). But
smc_connect_decline_fallback() has already dropped that reference.
Would one of these work better?
- unhash a socket whose conn was freed by smc_conn_abort(), or
otherwise hide it from diag
- cancel close_work, or check conn->freed in smc_cdc_rx_handler() and
in the diag reader
- pin the lgr for as long as stale conn state is visible
> if ((req->diag_ext & (1 << (SMC_DIAG_CONNINFO - 1))) &&
> smc->conn.alert_token_local) {
[Severity: Medium]
This is a pre-existing issue and wasn't introduced by this patch. Is
req->diag_ext guaranteed to be inside the request payload here?
smc_diag_handler_dump() starts the dump after checking only:
if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY &&
h->nlmsg_flags & NLM_F_DUMP) {
The generic __sock_diag_cmd() only checks for a struct sock_diag_req:
if (nlmsg_len(nlh) < sizeof(*req))
return -EINVAL;
That struct is 2 bytes. So an unprivileged SOCK_DIAG_BY_FAMILY |
NLM_F_DUMP request for AF_SMC with a 2 byte payload is accepted. diag_ext
is at payload offset 3, past skb->len, in tailroom that
netlink_sendmsg() never initialized.
Those uninitialized bits then decide whether SMC_DIAG_CONNINFO,
SMC_DIAG_LGRINFO and SMC_DIAG_DMBINFO are emitted. That exposes the bits
to the requester, and KMSAN would report it as uninit-value.
inet_diag and unix_diag reject requests shorter than their family
request struct. Should smc_diag_handler_dump() also reject
nlmsg_len(h) < sizeof(struct smc_diag_req)?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261028070210.1685136-1-mjambigi%40linux.ibm.com
prev parent reply other threads:[~2026-11-01 7:15 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 7:02 [PATCH net v14] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
2026-10-28 7:05 ` netdev-bot+sinfo
2026-11-01 7:15 ` 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=179351735189.3131.9219259330792018704@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=alibuda@linux.alibaba.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=dust.li@linux.alibaba.com \
--cc=edumazet@google.com \
--cc=guwen@linux.alibaba.com \
--cc=hidayath@linux.ibm.com \
--cc=horms@kernel.org \
--cc=kuba@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=pabeni@redhat.com \
--cc=pasic@linux.ibm.com \
--cc=sidraya@linux.ibm.com \
--cc=stable@vger.kernel.org \
--cc=tonylu@linux.alibaba.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