* [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race
@ 2026-10-15 8:13 Mahanta Jambigi
2026-10-15 8:19 ` netdev-bot+sinfo
2026-10-19 9:19 ` netdev-bot+sashiko
0 siblings, 2 replies; 5+ messages in thread
From: Mahanta Jambigi @ 2026-10-15 8:13 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya
Cc: hidayath, pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
linux-rdma, Mahanta Jambigi
The diag dump walks the socket hash table under a read_lock and dereferences
conn->lgr and conn->lnk. Four paths expose a window where a hashed socket has
freed or partially freed conn/lgr/lnk state.
First, smc_conn_abort() calls smc_conn_free() during early handshake aborts
while the socket is still hashed in SMC_INIT, synchronously freeing the lgr on
the smc_lgr_cleanup_early() path without holding the hash write_lock. A
concurrent diag reader can load a non-NULL conn->lgr and dereference
lgr->is_smcd after the kfree. Skip the connection, link group and DMB attributes
in __smc_diag_dump() for sockets in SMC_INIT or SMC_CLOSED, and do not look at
conn->lgr to determine diag_mode in SMC_INIT.
This changes the sock_diag output for such sockets. SMC_DIAG_CONNINFO,
SMC_DIAG_LGRINFO and SMC_DIAG_DMBINFO are no longer reported while a socket is
in SMC_INIT or SMC_CLOSED, and diag_mode is SMC_DIAG_MODE_SMCR for a
non-fallback SMC-D socket in SMC_INIT, as it already was before
smc_conn_create() had run. smcss does not use these attributes for such sockets.
Use smp_load_acquire() / smp_store_release() on sk_state to order these checks
on weakly ordered CPUs.
Second, in smc_conn_kill() on SMC-D with dmb_nocopy, smcd_buf_detach() NULLs and
frees conn->sndbuf_desc while the socket is still hashed. A concurrent diag
reader entering the CONNINFO block can dereference the freed descriptor. Unhash
the socket right before smcd_buf_detach(). Other kill paths leave the socket
hashed, since it can stay open until close().
Third, in the terminal teardown branches of smc_close_active_abort()
(PEERCLOSEWAIT and PROCESSABORT groups) and in smc_close_passive_work(),
smc_conn_free() drops lgr and lnk references while the socket is still hashed.
Unhash immediately before each smc_conn_free() call at those two sites.
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. Use smp_store_release() to order the
alert_token_local clear before the sk_state = SMC_CLOSED store.
Additionally, in smc_conn_free(), unregister the connection unconditionally so
alert_token_local is always cleared and the connection is removed from the link
group's tree even when the link group was already unlinked from the global list
during handshake abort or termination (such as server CLC decline fallback).
Finally, in __smc_diag_dump(), copy the cached link->ibname instead of
dereferencing link->smcibdev->ibdev->name to avoid the NULL pointer dereference
/ UAF if __smcr_link_clear() zeroes the link during concurrent SMC-R link
failover. Like the SMC netlink link dump and the tracepoints, this reports the
name at link creation time and does not follow a later ib_device_rename().
Fixes: f16a7dd5cf27 ("smc: netlink interface for SMC sockets")
Fixes: 2a0674fffb6b ("net/smc: improve abnormal termination of link groups")
Fixes: 9dbe086c69b8 ("net/smc: fix invalid link access in dumping SMC-R connections")
Fixes: ae2be35cbed2 ("net/smc: {at|de}tach sndbuf to peer DMB if supported")
Cc: stable@vger.kernel.org
Reviewed-by: Hidayath Khan <hidayath@linux.ibm.com>
Reviewed-by: Sidraya Jayagond <sidraya@linux.ibm.com>
Signed-off-by: Mahanta Jambigi <mjambigi@linux.ibm.com>
---
Changes in v11:
- smc_core.c: unregister connection unconditionally in smc_conn_free()
rather than gating it on !list_empty(&lgr->list), ensuring
alert_token_local is always reset to 0
- commit message: add Fixes tag for 2a0674fffb6b and clarify that
copying cached link->ibname eliminates the NULL dereference / UAF
during concurrent link clearing
Changes in v10:
- smc_core.c: move the unhash out of the top of smc_conn_kill() into the
smc_ism_support_dmb_nocopy() branch, right before smcd_buf_detach(); the
other kill paths leave ACTIVE/APPCLOSEWAIT sockets open, so they must
stay visible to smcss and keep their inuse accounting
- commit message: document the sock_diag output changes for sockets in
SMC_INIT and SMC_CLOSED (extension attributes omitted, diag_mode of
SMC-D in SMC_INIT) and that ibname is the name cached at link creation
Changes in v9:
- af_smc.c: change plain store in smc_listen_out_err() to
smp_store_release(&newsmcsk->sk_state, SMC_CLOSED) to order the
preceding alert_token_local clear (done under write_lock_bh() inside
smc_conn_free()) before the sk_state write; pairs with the
smp_load_acquire() in __smc_diag_dump() to close a use-after-free on
weakly ordered CPUs where a diag reader could observe SMC_CLOSED while
conn->lgr still appeared live
Changes in v8:
- smc_diag.c: use smp_load_acquire(&sk->sk_state) to prevent
speculative load reordering on weakly ordered CPUs (such as arm64)
during handshake abort fallback
- af_smc.c: pair with smp_store_release(&sk->sk_state, SMC_ACTIVE)
when transitioning out of SMC_INIT
- smc_diag.c: copy cached link->ibname instead of chasing
link->smcibdev->ibdev->name to prevent NULL pointer dereference / UAF
if __smcr_link_clear() zeroes the link during link failover
Changes in v7:
- no code changes; commit message only
- document the SMC_CLOSED leg of the early-exit guard and the rationale
that smcss already skips CLOSED sockets on the userspace side; add
Fixes: ae2be35cbed2 for the smc_conn_kill() hunk since
smcd_buf_detach() was introduced by that commit
Changes in v6:
- smc_diag.c: inline sk->sk_state != SMC_INIT into the else-if
condition so conn->lgr->is_smcd is never loaded for SMC_INIT sockets;
retain the SMC_INIT || SMC_CLOSED early-exit guard and out: label
from v5 to defensively skip the CONNINFO/LGRINFO/DMBINFO blocks for
sockets with no live connection
- smc_close.c: remove the unconditional unhash from the top of
smc_close_active_abort(); place it scoped immediately before each of
the two smc_conn_free() calls in the PEERCLOSEWAIT and PROCESSABORT
branches, and before smc_conn_free() in smc_close_passive_work();
this avoids prematurely hiding live SMC_ACTIVE/APPCLOSEWAIT sockets
from smcss
- smc_core.c: unhash at the top of smc_conn_kill(), before
smcd_buf_detach(), so the socket is off the hash table before
conn->sndbuf_desc is freed on the dmb_nocopy path
Changes in v5:
- guard conn/lgr/lnk blocks in __smc_diag_dump() with an
(SMC_INIT || SMC_CLOSED) state check placed after the
SMC_DIAG_FALLBACK nla_put; SMC_INIT can coexist with fallback
(smc_sendmsg() MSG_FASTOPEN path), so the guard must not suppress
the fallback reason
Changes in v4:
- dropped smc_conn_unhash() wrapper, conn->unhashed flag, and all
changes to af_smc.c, smc.h, smc_core.c and smc_core.h;
smc_unhash_sk() is already idempotent via sk_hashed(), so direct
calls at the two teardown sites in smc_close.c are sufficient
- dropped the __smc_release() hunk: it needs no change since the
subsequent unhash there is already a safe no-op
- fixed premature-unhash issue present in v3: smc_conn_free() must not
unhash because smc_conn_abort() calls it before
smc_switch_to_fallback() in both smc_listen_decline() and
smc_connect_rdma() error paths; unhashing there would make live
fallback sockets invisible to smcss
- likewise, the ISM/RDMA retry loops call smc_conn_abort() on a failed
attempt and then smc_conn_create() on the next device; unhashing in
smc_conn_free() would permanently hide the established connection from
smc_diag since smc_conn_create() does not re-hash the socket
Changes in v3:
- redesigned as a single patch; dropped the lgr_lnk_lock spinlock
approach and the 2-patch split
Changes in v2:
- split into a 2-patch series; patch 1/2 adds per-connection
lgr_lnk_lock infrastructure to smc_core, patch 2/2 fixes the diag
dump path using it
net/smc/af_smc.c | 10 +++++++---
net/smc/smc_close.c | 3 +++
net/smc/smc_core.c | 6 ++++--
net/smc/smc_diag.c | 12 ++++++++----
4 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index e9f93b3ab435..41b8af13efc4 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -976,7 +976,7 @@ static int smc_connect_fallback(struct smc_sock *smc, int reason_code)
smc_copy_sock_settings_to_clc(smc);
smc->connect_nonblock = 0;
if (smc->sk.sk_state == SMC_INIT)
- smc->sk.sk_state = SMC_ACTIVE;
+ smp_store_release(&smc->sk.sk_state, SMC_ACTIVE);
return 0;
}
@@ -1381,7 +1381,7 @@ static int smc_connect_rdma(struct smc_sock *smc,
smc_copy_sock_settings_to_clc(smc);
smc->connect_nonblock = 0;
if (smc->sk.sk_state == SMC_INIT)
- smc->sk.sk_state = SMC_ACTIVE;
+ smp_store_release(&smc->sk.sk_state, SMC_ACTIVE);
return 0;
connect_abort:
@@ -1484,7 +1484,7 @@ static int smc_connect_ism(struct smc_sock *smc,
smc_copy_sock_settings_to_clc(smc);
smc->connect_nonblock = 0;
if (smc->sk.sk_state == SMC_INIT)
- smc->sk.sk_state = SMC_ACTIVE;
+ smp_store_release(&smc->sk.sk_state, SMC_ACTIVE);
return 0;
connect_abort:
@@ -1951,7 +1951,7 @@ static void smc_listen_out_connected(struct smc_sock *new_smc)
struct sock *newsmcsk = &new_smc->sk;
if (newsmcsk->sk_state == SMC_INIT)
- newsmcsk->sk_state = SMC_ACTIVE;
+ smp_store_release(&newsmcsk->sk_state, SMC_ACTIVE);
smc_listen_out(new_smc);
}
@@ -1965,7 +1965,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);
smc_listen_out(new_smc);
}
diff --git a/net/smc/smc_close.c b/net/smc/smc_close.c
index bb0313ef5f7c..aa6363abdd74 100644
--- a/net/smc/smc_close.c
+++ b/net/smc/smc_close.c
@@ -154,6 +154,7 @@ void smc_close_active_abort(struct smc_sock *smc)
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
+ sk->sk_prot->unhash(sk);
smc_conn_free(&smc->conn);
release_clcsock = true;
sock_put(sk); /* passive closing */
@@ -165,6 +166,7 @@ void smc_close_active_abort(struct smc_sock *smc)
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
+ sk->sk_prot->unhash(sk);
smc_conn_free(&smc->conn);
release_clcsock = true;
break;
@@ -433,6 +435,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);
if (smc->clcsock)
release_clcsock = true;
diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
index 9974149659c2..6735f185cf48 100644
--- a/net/smc/smc_core.c
+++ b/net/smc/smc_core.c
@@ -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);
@@ -1530,8 +1529,10 @@ static void smc_conn_kill(struct smc_connection *conn, bool soft)
tasklet_kill(&conn->rx_tsklet);
else
tasklet_unlock_wait(&conn->rx_tsklet);
- if (smc_ism_support_dmb_nocopy(conn->lgr->smcd))
+ if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) {
+ smc->sk.sk_prot->unhash(&smc->sk);
smcd_buf_detach(conn);
+ }
} else {
smc_cdc_wait_pend_tx_wr(conn);
}
diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
index bf0beaa23bdb..36a3b8f31f28 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;
@@ -103,6 +104,9 @@ 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)
+ goto out;
+
if ((req->diag_ext & (1 << (SMC_DIAG_CONNINFO - 1))) &&
smc->conn.alert_token_local) {
struct smc_connection *conn = &smc->conn;
@@ -154,8 +158,7 @@ static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
.lnk[0].link_id = link->link_id,
};
- memcpy(linfo.lnk[0].ibname, link->smcibdev->ibdev->name,
- sizeof(link->smcibdev->ibdev->name));
+ memcpy(linfo.lnk[0].ibname, link->ibname, sizeof(link->ibname));
smc_gid_be16_convert(linfo.lnk[0].gid, link->gid);
smc_gid_be16_convert(linfo.lnk[0].peer_gid, link->peer_gid);
@@ -185,6 +188,7 @@ static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
goto errout;
}
+out:
nlmsg_end(skb, nlh);
return 0;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race
2026-10-15 8:13 [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
@ 2026-10-15 8:19 ` netdev-bot+sinfo
2026-10-15 8:21 ` Mahanta Jambigi
2026-10-19 9:19 ` netdev-bot+sashiko
1 sibling, 1 reply; 5+ messages in thread
From: netdev-bot+sinfo @ 2026-10-15 8:19 UTC (permalink / raw)
To: Mahanta Jambigi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath, pasic, horms, tonylu, guwen, stable, netdev,
linux-s390, linux-rdma
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race
2026-10-15 8:19 ` netdev-bot+sinfo
@ 2026-10-15 8:21 ` Mahanta Jambigi
0 siblings, 0 replies; 5+ messages in thread
From: Mahanta Jambigi @ 2026-10-15 8:21 UTC (permalink / raw)
To: netdev-bot+sinfo
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath, pasic, horms, tonylu, guwen, stable, netdev,
linux-s390, linux-rdma
On 15/10/26 1:49 pm, netdev-bot+sinfo@kernel.org wrote:
> Hi!
>
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
>
> - How the issue was discovered, e.g. hit in production, hit during
> development, syzbot report, manual code inspection, LLM or static
> analysis tool scan.
The issue was found by manual code inspection while reviewing the diag
dump path in net/smc/smc_diag.c.
>
> - Whether the issue was actually triggered, or is only theoretical
> (e.g. found by code inspection). If it was triggered please include
> the symptoms, like the stack trace or error messages.
The races are theoretical — none of the three windows were triggered in
practice. The smc_conn_kill() / smcd_buf_detach() race requires an
active smcss poll to coincide with an SMC-D lgr termination on the
dmb_nocopy path; the smc_close_active_abort() and
smc_close_passive_work() races require a concurrent diag dump during the
narrow teardown window between sk_state = SMC_CLOSED and
smc_conn_free(). No stack trace or error message is available since the
bugs were not hit in a running system.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race
2026-10-15 8:13 [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
2026-10-15 8:19 ` netdev-bot+sinfo
@ 2026-10-19 9:19 ` netdev-bot+sashiko
2026-10-19 11:07 ` Mahanta Jambigi
1 sibling, 1 reply; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 9:19 UTC (permalink / raw)
To: mjambigi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath, pasic, horms, tonylu, guwen, stable, netdev,
linux-s390, linux-rdma
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 1 · High: 0 · Medium: 0 · 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).
- [Critical] The patch makes smc_conn_free() call
smc_lgr_unregister_conn() unconditionally.
--
net/smc: fix lgr/lnk lifetime vs diag reader race
This patch closes races between the SMC sock_diag dump and teardown of
conn/lgr/lnk state. It unhashes sockets before smc_conn_free() and
smcd_buf_detach(), orders sk_state with acquire/release, skips the
extension attributes for sockets in SMC_INIT/SMC_CLOSED, and uses the
cached link->ibname. It also makes smc_conn_free() always call
smc_lgr_unregister_conn().
> diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
> index 9974149659c2..6735f185cf48 100644
> --- a/net/smc/smc_core.c
> +++ b/net/smc/smc_core.c
> @@ -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]
Now that the unregister is unconditional, can lgr->free_work get re-armed
on a link group that is already being torn down? If so, the lgr could be
freed while that timer is still pending.
smc_lgr_cleanup_early() unlinks the lgr but never sets lgr->freeing:
smc_lgr_cleanup_early() {
...
if (!list_empty(&lgr->list))
list_del_init(&lgr->list);
spin_unlock_bh(lgr_lock);
__smc_lgr_terminate(lgr, true);
}
smc_lgr_schedule_free_work() checks only lgr->freeing, not
lgr->terminating:
if (!lgr->freeing) {
mod_delayed_work(system_percpu_wq, &lgr->free_work, ...
__smc_lgr_terminate() cancels free_work once, before a loop that sleeps
in lock_sock(). After the loop it calls smc_lgr_free(), and nothing on
that path cancels free_work again:
__smc_lgr_terminate() {
...
cancel_delayed_work(&lgr->free_work);
lgr->terminating = 1;
...
lock_sock(&smc->sk);
smc_conn_kill(conn, soft);
...
smc_lgr_cleanup(lgr);
smc_lgr_free(lgr);
}
One possible sequence on an SMC-D server:
- Conn X creates a first-contact lgr L. smc_listen_work() releases
smc_server_lgr_pending for SMC-D right after sending ACCEPT.
- Conn Y joins L. smc_conn_create() has no conns_num limit for SMC-D.
- X's CONFIRM fails:
smc_listen_decline(X) -> smc_conn_abort() -> smc_conn_free(X), while
L is still listed. Then smc_lgr_cleanup_early(L) runs list_del_init()
and __smc_lgr_terminate(L) cancels free_work, sets terminating = 1 and
blocks in lock_sock(Y). Y's listen worker holds that lock while it
waits for its own CONFIRM.
- Y's CONFIRM also fails:
smc_listen_decline(Y) -> smc_conn_abort() -> smc_conn_free(Y). The
lgr list is now empty, but with this change smc_lgr_unregister_conn(Y)
still runs. conns_num drops to 0, freeing is 0, so
smc_lgr_schedule_free_work() arms free_work with
SMC_LGR_FREE_DELAY_SERV.
- X's worker resumes, the loop ends, and smc_lgr_free(L) ->
smc_lgr_put() -> __smc_lgr_free() -> kfree(L) runs while free_work is
still pending.
Would the timer then fire on freed memory? If L were still alive at that
point, smc_lgr_free_work() would see freeing == 0 and an empty conns_all
and call smc_lgr_free() a second time.
Before this patch, smc_conn_free(Y) skipped smc_lgr_unregister_conn() when
the lgr list was empty, so conns_num stayed at 1 and free_work was not
re-armed.
The peer decides whether and when each CONFIRM fails, and with
loopback-ism the client can be a local unprivileged user.
Should smc_lgr_cleanup_early() set lgr->freeing like the other unlink
paths do? Alternatively, smc_lgr_schedule_free_work() could also check
lgr->terminating.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015081349.1252391-1-mjambigi%40linux.ibm.com
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race
2026-10-19 9:19 ` netdev-bot+sashiko
@ 2026-10-19 11:07 ` Mahanta Jambigi
0 siblings, 0 replies; 5+ messages in thread
From: Mahanta Jambigi @ 2026-10-19 11:07 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath, pasic, horms, tonylu, guwen, stable, netdev,
linux-s390, linux-rdma
On 19/10/26 2:49 pm, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 1 · High: 0 · Medium: 0 · 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).
>
> - [Critical] The patch makes smc_conn_free() call
> smc_lgr_unregister_conn() unconditionally.
>
> --
>
> net/smc: fix lgr/lnk lifetime vs diag reader race
>
> This patch closes races between the SMC sock_diag dump and teardown of
> conn/lgr/lnk state. It unhashes sockets before smc_conn_free() and
> smcd_buf_detach(), orders sk_state with acquire/release, skips the
> extension attributes for sockets in SMC_INIT/SMC_CLOSED, and uses the
> cached link->ibname. It also makes smc_conn_free() always call
> smc_lgr_unregister_conn().
>
>> diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
>> index 9974149659c2..6735f185cf48 100644
>> --- a/net/smc/smc_core.c
>> +++ b/net/smc/smc_core.c
>> @@ -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]
> Now that the unregister is unconditional, can lgr->free_work get re-armed
> on a link group that is already being torn down? If so, the lgr could be
> freed while that timer is still pending.
>
> smc_lgr_cleanup_early() unlinks the lgr but never sets lgr->freeing:
>
> smc_lgr_cleanup_early() {
> ...
> if (!list_empty(&lgr->list))
> list_del_init(&lgr->list);
> spin_unlock_bh(lgr_lock);
> __smc_lgr_terminate(lgr, true);
> }
>
> smc_lgr_schedule_free_work() checks only lgr->freeing, not
> lgr->terminating:
>
> if (!lgr->freeing) {
> mod_delayed_work(system_percpu_wq, &lgr->free_work, ...
>
> __smc_lgr_terminate() cancels free_work once, before a loop that sleeps
> in lock_sock(). After the loop it calls smc_lgr_free(), and nothing on
> that path cancels free_work again:
>
> __smc_lgr_terminate() {
> ...
> cancel_delayed_work(&lgr->free_work);
> lgr->terminating = 1;
> ...
> lock_sock(&smc->sk);
> smc_conn_kill(conn, soft);
> ...
> smc_lgr_cleanup(lgr);
> smc_lgr_free(lgr);
> }
>
> One possible sequence on an SMC-D server:
>
> - Conn X creates a first-contact lgr L. smc_listen_work() releases
> smc_server_lgr_pending for SMC-D right after sending ACCEPT.
>
> - Conn Y joins L. smc_conn_create() has no conns_num limit for SMC-D.
>
> - X's CONFIRM fails:
> smc_listen_decline(X) -> smc_conn_abort() -> smc_conn_free(X), while
> L is still listed. Then smc_lgr_cleanup_early(L) runs list_del_init()
> and __smc_lgr_terminate(L) cancels free_work, sets terminating = 1 and
> blocks in lock_sock(Y). Y's listen worker holds that lock while it
> waits for its own CONFIRM.
>
> - Y's CONFIRM also fails:
> smc_listen_decline(Y) -> smc_conn_abort() -> smc_conn_free(Y). The
> lgr list is now empty, but with this change smc_lgr_unregister_conn(Y)
> still runs. conns_num drops to 0, freeing is 0, so
> smc_lgr_schedule_free_work() arms free_work with
> SMC_LGR_FREE_DELAY_SERV.
>
> - X's worker resumes, the loop ends, and smc_lgr_free(L) ->
> smc_lgr_put() -> __smc_lgr_free() -> kfree(L) runs while free_work is
> still pending.
>
> Would the timer then fire on freed memory? If L were still alive at that
> point, smc_lgr_free_work() would see freeing == 0 and an empty conns_all
> and call smc_lgr_free() a second time.
>
> Before this patch, smc_conn_free(Y) skipped smc_lgr_unregister_conn() when
> the lgr list was empty, so conns_num stayed at 1 and free_work was not
> re-armed.
>
> The peer decides whether and when each CONFIRM fails, and with
> loopback-ism the client can be a local unprivileged user.
>
> Should smc_lgr_cleanup_early() set lgr->freeing like the other unlink
> paths do? Alternatively, smc_lgr_schedule_free_work() could also check
> lgr->terminating.
>
> [ ... ]
>
You are correct. v11 introduced unconditional smc_lgr_unregister_conn()
which correctly fixes the alert_token_local UAF, but left
smc_lgr_schedule_free_work() with only the !lgr->freeing check. On the
smc_lgr_cleanup_early() path, __smc_lgr_terminate() sets terminating=1
and cancels free_work without setting freeing=1, so a concurrent
smc_conn_free() decrementing conns_num to 0 would re-arm free_work. The
subsequent kfree(lgr) inside __smc_lgr_terminate() → smc_lgr_free()
leaves the re-armed work pointing at freed memory.
Fixed in v12 by adding !lgr->terminating to the guard in
smc_lgr_schedule_free_work():
- if (!lgr->freeing) {
+ if (!lgr->freeing && !lgr->terminating) {
This is safe: on the smc_lgr_cleanup_early() path __smc_lgr_terminate()
walks the full conns_all tree, kills every connection via
smc_conn_kill(), and then calls smc_lgr_free() unconditionally — it does
not rely on free_work to complete the teardown. Blocking the re-arm when
terminating=1 just prevents the UAF; the LGR is still freed by
__smc_lgr_terminate() itself.
On all other teardown paths (smc_lgr_terminate_sched,
smc_smcd_terminate, smc_smcd_terminate_all, smc_smcr_terminate_all)
freeing=1 is set before __smc_lgr_terminate() or terminate_work is
scheduled, so !lgr->freeing already blocked smc_lgr_schedule_free_work()
there — the !lgr->terminating addition changes nothing for those paths.
pw-bot: cr
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-19 11:07 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 8:13 [PATCH net v11] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
2026-10-15 8:19 ` netdev-bot+sinfo
2026-10-15 8:21 ` Mahanta Jambigi
2026-10-19 9:19 ` netdev-bot+sashiko
2026-10-19 11:07 ` Mahanta Jambigi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox