* [PATCH net v2 1/2] net/smc: add connection lifetime infrastructure for diag
2026-08-28 6:54 [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Mahanta Jambigi
@ 2026-08-28 6:54 ` Mahanta Jambigi
2026-08-28 6:54 ` [PATCH net v2 2/2] net/smc: fix races in smc_diag dump path Mahanta Jambigi
1 sibling, 0 replies; 3+ messages in thread
From: Mahanta Jambigi @ 2026-08-28 6:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath
Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
linux-rdma, Mahanta Jambigi
Add the basic lifetime synchronization needed by the diag dump path. Introduce
a per-connection lock that serializes connection-owned conn->lgr/conn->lnk
updates against readers and use the new lock in the switch/free handoff paths.
In smc_conn_free(), NULL out conn->lgr and conn->lnk under lgr_lnk_lock before
releasing the borrowed references. This closes the window where a concurrent
diag reader could observe a non-NULL conn->lgr that points at already-freed
memory.
A non-NULL conn->lgr seen under lgr_lnk_lock guarantees the connection-owned lgr
reference is still held and the object is alive; conn->lnk is likewise valid
when non-NULL under the lock. The follow-on diag fix relies on this invariant to
safely read lgr and link fields without borrowing an extra reference.
Reviewed-by: Hidayath Khan <hidayath@linux.ibm.com>
Signed-off-by: Mahanta Jambigi <mjambigi@linux.ibm.com>
---
net/smc/af_smc.c | 1 +
net/smc/smc.h | 1 +
net/smc/smc_core.c | 10 +++++++++-
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index e9f93b3ab435..8eece96a9edf 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -408,6 +408,7 @@ void smc_sk_init(struct net *net, struct sock *sk, int protocol)
sock_lock_init_class_and_name(sk, "slock-AF_SMC", &smc_slock_key,
"sk_lock-AF_SMC", &smc_key);
spin_lock_init(&smc->accept_q_lock);
+ spin_lock_init(&smc->conn.lgr_lnk_lock);
spin_lock_init(&smc->conn.send_lock);
mutex_init(&smc->clcsock_release_lock);
smc_init_saved_callbacks(smc);
diff --git a/net/smc/smc.h b/net/smc/smc.h
index 52145df83f6e..d5fb5d92809b 100644
--- a/net/smc/smc.h
+++ b/net/smc/smc.h
@@ -232,6 +232,7 @@ struct smc_connection {
atomic_t sndbuf_space; /* remaining space in sndbuf */
u16 tx_cdc_seq; /* sequence # for CDC send */
u16 tx_cdc_seq_fin; /* sequence # - tx completed */
+ spinlock_t lgr_lnk_lock; /* protect conn owned lgr/lnk refs */
spinlock_t send_lock; /* protect wr_sends */
atomic_t cdc_pend_tx_wr; /* number of pending tx CDC wqe
* - inc when post wqe,
diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
index 181647982490..cc69b86b2ee5 100644
--- a/net/smc/smc_core.c
+++ b/net/smc/smc_core.c
@@ -1083,6 +1083,7 @@ static int smc_switch_cursor(struct smc_sock *smc, struct smc_cdc_tx_pend *pend,
void smc_switch_link_and_count(struct smc_connection *conn,
struct smc_link *to_lnk)
{
+ spin_lock_bh(&conn->lgr_lnk_lock);
atomic_dec(&conn->lnk->conn_cnt);
/* link_hold in smc_conn_create() */
smcr_link_put(conn->lnk);
@@ -1090,6 +1091,7 @@ void smc_switch_link_and_count(struct smc_connection *conn,
atomic_inc(&conn->lnk->conn_cnt);
/* link_put in smc_conn_free() */
smcr_link_hold(conn->lnk);
+ spin_unlock_bh(&conn->lgr_lnk_lock);
}
struct smc_link *smc_switch_conns(struct smc_link_group *lgr,
@@ -1255,6 +1257,7 @@ static void smc_buf_unuse(struct smc_connection *conn,
void smc_conn_free(struct smc_connection *conn)
{
struct smc_link_group *lgr = conn->lgr;
+ struct smc_link *lnk;
if (!lgr || conn->freed)
/* Connection has never been registered in a
@@ -1287,8 +1290,13 @@ void smc_conn_free(struct smc_connection *conn)
if (!lgr->conns_num)
smc_lgr_schedule_free_work(lgr);
lgr_put:
+ spin_lock_bh(&conn->lgr_lnk_lock);
+ lnk = conn->lnk;
+ conn->lnk = NULL;
+ conn->lgr = NULL;
+ spin_unlock_bh(&conn->lgr_lnk_lock);
if (!lgr->is_smcd)
- smcr_link_put(conn->lnk); /* link_hold in smc_conn_create() */
+ smcr_link_put(lnk); /* link_hold in smc_conn_create() */
smc_lgr_put(lgr); /* lgr_hold in smc_conn_create() */
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH net v2 2/2] net/smc: fix races in smc_diag dump path
2026-08-28 6:54 [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Mahanta Jambigi
2026-08-28 6:54 ` [PATCH net v2 1/2] net/smc: add connection lifetime infrastructure for diag Mahanta Jambigi
@ 2026-08-28 6:54 ` Mahanta Jambigi
1 sibling, 0 replies; 3+ messages in thread
From: Mahanta Jambigi @ 2026-08-28 6:54 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, alibuda, dust.li,
sidraya, hidayath
Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
linux-rdma, Mahanta Jambigi
Two races exist in the SMC diag dump path.
Race 1: smc->clcsock can be set to NULL by smc_clcsock_release() after a bare
NULL check in smc_diag_msg_common_fill() and before the subsequent field reads.
Use mutex_trylock() on clcsock_release_lock to serialize the check and the reads
with that writer. If the lock cannot be taken, teardown or fallback is in
progress; leaving address fields zeroed is a safe transient gap for a monitoring
tool.
Race 2: conn->lgr and conn->lnk can be torn down concurrently by
smc_conn_free(). Take conn->lgr_lnk_lock when accessing these fields in the dump
path. This covers three sites: the diag_mode classification
(conn->lgr->is_smcd), the SMC_DIAG_LGRINFO block, and the SMC_DIAG_DMBINFO
block. In each case, fields are snapshotted into local variables under the lock
and used after releasing it. With patch 1/2, a non-NULL conn->lgr seen under
that lock guarantees the lgr is alive. conn->lnk is likewise valid when non-NULL
under the lock. nla_put() is called after releasing the lock.
This design avoids borrowing an extra lgr reference, which would require
smc_lgr_put() to be called inside __smc_diag_dump() -- a problem because
smc_diag_dump_proto() holds an atomic read_lock for the duration of that call.
Fixes: f16a7dd5cf27 ("smc: netlink interface for SMC sockets")
Fixes: 9dbe086c69b8 ("net/smc: fix invalid link access in dumping SMC-R connections")
Reviewed-by: Hidayath Khan <hidayath@linux.ibm.com>
Signed-off-by: Mahanta Jambigi <mjambigi@linux.ibm.com>
diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
index bf0beaa23bdb..06db3d769929 100644
--- a/net/smc/smc_diag.c
+++ b/net/smc/smc_diag.c
@@ -39,8 +39,20 @@ static void smc_diag_msg_common_fill(struct smc_diag_msg *r, struct sock *sk)
memset(r, 0, sizeof(*r));
r->diag_family = sk->sk_family;
sock_diag_save_cookie(sk, r->id.idiag_cookie);
- if (!smc->clcsock)
+ /* smc_clcsock_release() sets smc->clcsock = NULL under
+ * clcsock_release_lock. Use mutex_trylock() to make the NULL check and all
+ * field reads atomic with that writer. mutex_trylock() is safe under the
+ * hash read_lock held by smc_diag_dump_proto() because it never sleeps. If
+ * it fails, the socket's clcsock is being modified or released (teardown
+ * or fallback transition); leaving address fields zeroed is a safe
+ * transient gap for a monitoring tool.
+ */
+ if (!mutex_trylock(&smc->clcsock_release_lock))
return;
+ if (!smc->clcsock) {
+ mutex_unlock(&smc->clcsock_release_lock);
+ return;
+ }
r->id.idiag_sport = htons(smc->clcsock->sk->sk_num);
r->id.idiag_dport = smc->clcsock->sk->sk_dport;
r->id.idiag_if = smc->clcsock->sk->sk_bound_dev_if;
@@ -55,6 +67,7 @@ static void smc_diag_msg_common_fill(struct smc_diag_msg *r, struct sock *sk)
sizeof(smc->clcsock->sk->sk_v6_daddr));
#endif
}
+ mutex_unlock(&smc->clcsock_release_lock);
}
static int smc_diag_msg_attrs_fill(struct sock *sk, struct sk_buff *skb,
@@ -88,12 +101,18 @@ 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;
- if (smc->use_fallback)
+ 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)
- r->diag_mode = SMC_DIAG_MODE_SMCD;
- else
- r->diag_mode = SMC_DIAG_MODE_SMCR;
+ } else {
+ bool is_smcd = false;
+
+ spin_lock_bh(&smc->conn.lgr_lnk_lock);
+ if (smc_conn_lgr_valid(&smc->conn))
+ is_smcd = smc->conn.lgr->is_smcd;
+ spin_unlock_bh(&smc->conn.lgr_lnk_lock);
+ r->diag_mode = is_smcd ? SMC_DIAG_MODE_SMCD
+ : SMC_DIAG_MODE_SMCR;
+ }
user_ns = sk_user_ns(NETLINK_CB(cb->skb).sk);
if (smc_diag_msg_attrs_fill(sk, skb, r, user_ns))
goto errout;
@@ -143,45 +162,57 @@ static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
goto errout;
}
- if (smc_conn_lgr_valid(&smc->conn) && !smc->conn.lgr->is_smcd &&
- (req->diag_ext & (1 << (SMC_DIAG_LGRINFO - 1))) &&
- !list_empty(&smc->conn.lgr->list)) {
- struct smc_link *link = smc->conn.lnk;
-
- struct smc_diag_lgrinfo linfo = {
- .role = smc->conn.lgr->role,
- .lnk[0].ibport = link->ibport,
- .lnk[0].link_id = link->link_id,
- };
-
- memcpy(linfo.lnk[0].ibname, link->smcibdev->ibdev->name,
- sizeof(link->smcibdev->ibdev->name));
- smc_gid_be16_convert(linfo.lnk[0].gid, link->gid);
- smc_gid_be16_convert(linfo.lnk[0].peer_gid, link->peer_gid);
-
- if (nla_put(skb, SMC_DIAG_LGRINFO, sizeof(linfo), &linfo) < 0)
+ if (req->diag_ext & (1 << (SMC_DIAG_LGRINFO - 1))) {
+ struct smc_connection *conn = &smc->conn;
+ struct smc_diag_lgrinfo linfo;
+ bool lgr_valid = false;
+
+ memset(&linfo, 0, sizeof(linfo));
+ spin_lock_bh(&conn->lgr_lnk_lock);
+ if (smc_conn_lgr_valid(conn) && !conn->lgr->is_smcd &&
+ conn->lnk && !list_empty(&conn->lgr->list)) {
+ linfo.role = conn->lgr->role;
+ linfo.lnk[0].ibport = conn->lnk->ibport;
+ linfo.lnk[0].link_id = conn->lnk->link_id;
+ memcpy(linfo.lnk[0].ibname,
+ conn->lnk->smcibdev->ibdev->name,
+ sizeof(conn->lnk->smcibdev->ibdev->name));
+ smc_gid_be16_convert(linfo.lnk[0].gid,
+ conn->lnk->gid);
+ smc_gid_be16_convert(linfo.lnk[0].peer_gid,
+ conn->lnk->peer_gid);
+ lgr_valid = true;
+ }
+ spin_unlock_bh(&conn->lgr_lnk_lock);
+ if (lgr_valid &&
+ nla_put(skb, SMC_DIAG_LGRINFO, sizeof(linfo), &linfo) < 0)
goto errout;
}
- if (smc_conn_lgr_valid(&smc->conn) && smc->conn.lgr->is_smcd &&
- (req->diag_ext & (1 << (SMC_DIAG_DMBINFO - 1))) &&
- !list_empty(&smc->conn.lgr->list) && smc->conn.rmb_desc) {
+ if (req->diag_ext & (1 << (SMC_DIAG_DMBINFO - 1))) {
struct smc_connection *conn = &smc->conn;
struct smcd_diag_dmbinfo dinfo;
- struct smcd_dev *smcd = conn->lgr->smcd;
- struct smcd_gid smcd_gid;
+ bool lgr_valid = false;
memset(&dinfo, 0, sizeof(dinfo));
-
- dinfo.linkid = *((u32 *)conn->lgr->id);
- dinfo.peer_gid = conn->lgr->peer_gid.gid;
- dinfo.peer_gid_ext = conn->lgr->peer_gid.gid_ext;
- copy_to_smcdgid(&smcd_gid, &smcd->dibs->gid);
- dinfo.my_gid = smcd_gid.gid;
- dinfo.my_gid_ext = smcd_gid.gid_ext;
- dinfo.token = conn->rmb_desc->token;
- dinfo.peer_token = conn->peer_token;
-
- if (nla_put(skb, SMC_DIAG_DMBINFO, sizeof(dinfo), &dinfo) < 0)
+ spin_lock_bh(&conn->lgr_lnk_lock);
+ if (smc_conn_lgr_valid(conn) && conn->lgr->is_smcd &&
+ conn->rmb_desc && !list_empty(&conn->lgr->list)) {
+ struct smcd_dev *smcd = conn->lgr->smcd;
+ struct smcd_gid smcd_gid;
+
+ dinfo.linkid = *((u32 *)conn->lgr->id);
+ dinfo.peer_gid = conn->lgr->peer_gid.gid;
+ dinfo.peer_gid_ext = conn->lgr->peer_gid.gid_ext;
+ copy_to_smcdgid(&smcd_gid, &smcd->dibs->gid);
+ dinfo.my_gid = smcd_gid.gid;
+ dinfo.my_gid_ext = smcd_gid.gid_ext;
+ dinfo.token = conn->rmb_desc->token;
+ dinfo.peer_token = conn->peer_token;
+ lgr_valid = true;
+ }
+ spin_unlock_bh(&conn->lgr_lnk_lock);
+ if (lgr_valid &&
+ nla_put(skb, SMC_DIAG_DMBINFO, sizeof(dinfo), &dinfo) < 0)
goto errout;
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 3+ messages in thread