Netdev List
 help / color / mirror / Atom feed
* [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
@ 2026-08-28  6:54 Mahanta Jambigi
  2026-08-28  6:54 ` [PATCH net v2 1/2] net/smc: add connection lifetime infrastructure for diag Mahanta Jambigi
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ 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

This series fixes multiple lifetime races in the SMC diag dump path.

The first patch adds the basic infrastructure needed to synchronize diag readers
against connection-owned conn->lgr/conn->lnk updates. It introduces a
per-connection spinlock and uses it in the link switch and connection free
handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
borrowed references are released, so a non-NULL conn->lgr seen under the lock
guarantees the lgr object is alive. The diag reader can rely on this invariant
without borrowing any extra reference.

The second patch fixes two races in smc_diag itself:

- serialize clcsock field access against smc_clcsock_release() with
  mutex_trylock()
- take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
  smc_conn_lgr_valid() inside the lock to check that the connection is
  still registered, then snapshot all required fields and call nla_put()
  after releasing the lock

Signed-off-by: Mahanta Jambigi <mjambigi@linux.ibm.com>
---
Changes in v2:
- this is v2 of the series; the earlier submission was mislabelled
  [PATCH v3] but was in fact the first version sent to the list
- 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
- dropped lock_sock()/release_sock() from __smc_diag_dump(); v1 held the
  socket lock across all lgr/lnk dereferences, requiring the hash read_lock
  to be dropped and re-acquired around each socket
- dropped the restart-from-head loop in smc_diag_dump_proto(); the new
  design does not drop the hash read_lock mid-walk so the hlist truncation
  concern no longer applies
- dropped refcount_inc_not_zero() socket pinning from the dump loop for the
  same reason: the hash read_lock is now held for the full walk
- added per-connection lgr_lnk_lock spinlock to struct smc_connection;
  conn->lgr and conn->lnk are NULLed under this lock in smc_conn_free()
  before borrowed references are released, establishing the invariant: a
  non-NULL conn->lgr seen under lgr_lnk_lock guarantees the lgr is alive
- added lgr_lnk_lock to smc_switch_link_and_count() to protect the conn->lnk
  pointer swap from concurrent diag readers
- replaced mutex_lock() on clcsock_release_lock in smc_diag_msg_common_fill()
  with mutex_trylock(); mutex_lock() was valid in v1 because the hash
  spinlock had been dropped, but the new design holds the hash read_lock
  throughout so only a non-sleeping trylock is safe; a failed trylock leaves
  address fields zeroed, which is acceptable for a monitoring tool
- all conn->lgr and conn->lnk accesses in __smc_diag_dump() use a
  snapshot-then-use pattern: fields are copied into local stack variables
  under lgr_lnk_lock and nla_put() is called after releasing the lock,
  avoiding any sleeping operation under the spinlock

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [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
  2026-08-31 13:42 ` [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Dust Li
  2 siblings, 0 replies; 10+ 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] 10+ 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
  2026-08-31 13:42 ` [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Dust Li
  2 siblings, 0 replies; 10+ 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] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  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 ` [PATCH net v2 2/2] net/smc: fix races in smc_diag dump path Mahanta Jambigi
@ 2026-08-31 13:42 ` Dust Li
  2026-08-31 14:08   ` Mahanta Jambigi
  2026-08-31 14:57   ` Mahanta Jambigi
  2 siblings, 2 replies; 10+ messages in thread
From: Dust Li @ 2026-08-31 13:42 UTC (permalink / raw)
  To: Mahanta Jambigi, andrew+netdev, davem, edumazet, kuba, pabeni,
	alibuda, sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma

On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>This series fixes multiple lifetime races in the SMC diag dump path.
>
>The first patch adds the basic infrastructure needed to synchronize diag readers
>against connection-owned conn->lgr/conn->lnk updates. It introduces a
>per-connection spinlock and uses it in the link switch and connection free
>handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>borrowed references are released, so a non-NULL conn->lgr seen under the lock
>guarantees the lgr object is alive. The diag reader can rely on this invariant
>without borrowing any extra reference.
>
>The second patch fixes two races in smc_diag itself:
>
>- serialize clcsock field access against smc_clcsock_release() with
>  mutex_trylock()
>- take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>  smc_conn_lgr_valid() inside the lock to check that the connection is
>  still registered, then snapshot all required fields and call nla_put()
>  after releasing the lock

Hi Mahanta,

As discussed in the other thread, I think we should defer the release of
smc->clcsock and remove clcsock_release_lock.

In that case, we should no longer need these two patches. Also,
introducing more locks in SMC is the last thing I want to do :)

Best regards,
Dust

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-08-31 13:42 ` [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Dust Li
@ 2026-08-31 14:08   ` Mahanta Jambigi
  2026-08-31 14:57   ` Mahanta Jambigi
  1 sibling, 0 replies; 10+ messages in thread
From: Mahanta Jambigi @ 2026-08-31 14:08 UTC (permalink / raw)
  To: dust.li, andrew+netdev, davem, edumazet, kuba, pabeni, alibuda,
	sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma



On 31/08/26 7:12 pm, Dust Li wrote:
> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>> This series fixes multiple lifetime races in the SMC diag dump path.
>>
>> The first patch adds the basic infrastructure needed to synchronize diag readers
>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>> per-connection spinlock and uses it in the link switch and connection free
>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>> without borrowing any extra reference.
>>
>> The second patch fixes two races in smc_diag itself:
>>
>> - serialize clcsock field access against smc_clcsock_release() with
>>  mutex_trylock()
>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>  still registered, then snapshot all required fields and call nla_put()
>>  after releasing the lock
> 
> Hi Mahanta,
> 
> As discussed in the other thread, I think we should defer the release of
> smc->clcsock and remove clcsock_release_lock.
> 
> In that case, we should no longer need these two patches. Also,
> introducing more locks in SMC is the last thing I want to do :)

Does your "[RFC net-next 0/7] net/smc: tie clcsock lifetime to the smc
socket and remove clcsock_release_lock" resolve conn.lgr/conn.lnk races
in __smc_diag_dump() against the concurrent tear-down paths?


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-08-31 13:42 ` [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Dust Li
  2026-08-31 14:08   ` Mahanta Jambigi
@ 2026-08-31 14:57   ` Mahanta Jambigi
  2026-09-01 13:02     ` Dust Li
  1 sibling, 1 reply; 10+ messages in thread
From: Mahanta Jambigi @ 2026-08-31 14:57 UTC (permalink / raw)
  To: dust.li, andrew+netdev, davem, edumazet, kuba, pabeni, alibuda,
	sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma



On 31/08/26 7:12 pm, Dust Li wrote:
> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>> This series fixes multiple lifetime races in the SMC diag dump path.
>>
>> The first patch adds the basic infrastructure needed to synchronize diag readers
>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>> per-connection spinlock and uses it in the link switch and connection free
>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>> without borrowing any extra reference.
>>
>> The second patch fixes two races in smc_diag itself:
>>
>> - serialize clcsock field access against smc_clcsock_release() with
>>  mutex_trylock()
>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>  still registered, then snapshot all required fields and call nla_put()
>>  after releasing the lock
> 
> Hi Mahanta,
> 
> As discussed in the other thread, I think we should defer the release of
> smc->clcsock and remove clcsock_release_lock.
> 
> In that case, we should no longer need these two patches. Also,
> introducing more locks in SMC is the last thing I want to do :)

Thanks for the new series "[RFC net-next 0/7] net/smc: tie clcsock
lifetime to the smc socket and remove clcsock_release_lock" — once it
lands, we can drop the mutex_trylock() fix for Race 1 (clcsock).

However, Race 2 remains open. Your series does not touch smc_core.c or
smc_cdc.c, so smc_conn_free(), smc_switch_link_and_count(), and
smc_cdc_msg_validate() still write conn->lgr/conn->lnk with no
synchronization against the diag reader.

On the lock concern — lgr_lnk_lock is a per-connection spinlock. It is
taken in smc_conn_free() (teardown), smc_switch_link_and_count() (link
failover), smc_cdc_msg_validate() (failover validation branch only, not
the normal CDC data path), and the diag reader. None of these are on the
per-message send/receive hot path. Could you explain what specifically
concerns you — lock ordering, memory footprint, or something else? That
would help us understand whether you have a different mechanism in mind.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-08-31 14:57   ` Mahanta Jambigi
@ 2026-09-01 13:02     ` Dust Li
  2026-09-01 15:13       ` Mahanta Jambigi
  0 siblings, 1 reply; 10+ messages in thread
From: Dust Li @ 2026-09-01 13:02 UTC (permalink / raw)
  To: Mahanta Jambigi, andrew+netdev, davem, edumazet, kuba, pabeni,
	alibuda, sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma

On 2026-08-31 20:27:38, Mahanta Jambigi wrote:
>
>
>On 31/08/26 7:12 pm, Dust Li wrote:
>> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>>> This series fixes multiple lifetime races in the SMC diag dump path.
>>>
>>> The first patch adds the basic infrastructure needed to synchronize diag readers
>>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>>> per-connection spinlock and uses it in the link switch and connection free
>>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>>> without borrowing any extra reference.
>>>
>>> The second patch fixes two races in smc_diag itself:
>>>
>>> - serialize clcsock field access against smc_clcsock_release() with
>>>  mutex_trylock()
>>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>>  still registered, then snapshot all required fields and call nla_put()
>>>  after releasing the lock
>> 
>> Hi Mahanta,
>> 
>> As discussed in the other thread, I think we should defer the release of
>> smc->clcsock and remove clcsock_release_lock.
>> 
>> In that case, we should no longer need these two patches. Also,
>> introducing more locks in SMC is the last thing I want to do :)
>
>Thanks for the new series "[RFC net-next 0/7] net/smc: tie clcsock
>lifetime to the smc socket and remove clcsock_release_lock" — once it
>lands, we can drop the mutex_trylock() fix for Race 1 (clcsock).
>
>However, Race 2 remains open. Your series does not touch smc_core.c or
>smc_cdc.c, so smc_conn_free(), smc_switch_link_and_count(), and
>smc_cdc_msg_validate() still write conn->lgr/conn->lnk with no
>synchronization against the diag reader.

Hi Mahanta,

Thanks for the detailed explanation. You are right that a per-connection
spinlock can work here, and I agree none of the lock sites are on the
per-message hot path. But I think we can also do the same thing we did with
clcsock_release_lock: instead of adding a lock, tie the lifetime of
conn->lgr/conn->lnk to the point where the connection stops being observable,
and remove the need for synchronization altogether.

For lgr/lnk, that point is the hash table. Once the connection is unhashed, the
diag dump (which iterates under the hash read_lock) can no longer reach it. So
if we make sure the connection-owned references are only dropped after unhash,
the invariant becomes: holding the hash read_lock and seeing a non-NULL
conn->lgr implies it is safe to dereference. The diag path then reduces to
`hold hash read_lock -> read conn->lgr -> if non-NULL, use it -> done` with no
new lock, no extra reference, and no trylock. The invariant is carried by
object lifetime rather than by a lock, which I find easier to keep correct over
time.

Best regards,
Dust


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-09-01 13:02     ` Dust Li
@ 2026-09-01 15:13       ` Mahanta Jambigi
  2026-09-02 12:16         ` Dust Li
  0 siblings, 1 reply; 10+ messages in thread
From: Mahanta Jambigi @ 2026-09-01 15:13 UTC (permalink / raw)
  To: dust.li, andrew+netdev, davem, edumazet, kuba, pabeni, alibuda,
	sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma



On 01/09/26 6:32 pm, Dust Li wrote:
> On 2026-08-31 20:27:38, Mahanta Jambigi wrote:
>>
>>
>> On 31/08/26 7:12 pm, Dust Li wrote:
>>> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>>>> This series fixes multiple lifetime races in the SMC diag dump path.
>>>>
>>>> The first patch adds the basic infrastructure needed to synchronize diag readers
>>>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>>>> per-connection spinlock and uses it in the link switch and connection free
>>>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>>>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>>>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>>>> without borrowing any extra reference.
>>>>
>>>> The second patch fixes two races in smc_diag itself:
>>>>
>>>> - serialize clcsock field access against smc_clcsock_release() with
>>>>  mutex_trylock()
>>>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>>>  still registered, then snapshot all required fields and call nla_put()
>>>>  after releasing the lock
>>>
>>> Hi Mahanta,
>>>
>>> As discussed in the other thread, I think we should defer the release of
>>> smc->clcsock and remove clcsock_release_lock.
>>>
>>> In that case, we should no longer need these two patches. Also,
>>> introducing more locks in SMC is the last thing I want to do :)
>>
>> Thanks for the new series "[RFC net-next 0/7] net/smc: tie clcsock
>> lifetime to the smc socket and remove clcsock_release_lock" — once it
>> lands, we can drop the mutex_trylock() fix for Race 1 (clcsock).
>>
>> However, Race 2 remains open. Your series does not touch smc_core.c or
>> smc_cdc.c, so smc_conn_free(), smc_switch_link_and_count(), and
>> smc_cdc_msg_validate() still write conn->lgr/conn->lnk with no
>> synchronization against the diag reader.
> 
> Hi Mahanta,
> 
> Thanks for the detailed explanation. You are right that a per-connection
> spinlock can work here, and I agree none of the lock sites are on the
> per-message hot path. But I think we can also do the same thing we did with
> clcsock_release_lock: instead of adding a lock, tie the lifetime of
> conn->lgr/conn->lnk to the point where the connection stops being observable,
> and remove the need for synchronization altogether.
> 
> For lgr/lnk, that point is the hash table. Once the connection is unhashed, the
> diag dump (which iterates under the hash read_lock) can no longer reach it. So
> if we make sure the connection-owned references are only dropped after unhash,
> the invariant becomes: holding the hash read_lock and seeing a non-NULL
> conn->lgr implies it is safe to dereference. The diag path then reduces to
> `hold hash read_lock -> read conn->lgr -> if non-NULL, use it -> done` with no
> new lock, no extra reference, and no trylock. The invariant is carried by
> object lifetime rather than by a lock, which I find easier to keep correct over
> time.

I looked carefully at the new design and found one remaining gap.

The *unhash* invariant — "any socket in the hash has its
connection-owned lgr/lnk refs held" — protects against smc_conn_free()
dropping refs while the socket is still hashed. However it does not
protect the conn->lnk->smcibdev->ibdev->name access in the
SMC_DIAG_LGRINFO block in smc_diag.c file.

The gap is in *smc_switch_link_and_count*(). It is called under
send_lock (not under any hash-related lock) and calls
smcr_link_put(conn->lnk) on the old link before reassigning conn->lnk.
If that put drops the last reference, __smcr_link_clear() runs
immediately, doing memset(lnk, 0, sizeof(struct smc_link)) which zeroes
lnk->smcibdev. The socket remains hashed throughout — so the *unhash*
invariant is not violated — but the diag reader can hold a stale pointer
to the old link and race this memset. The timeline:

diag reader [hash read_lock held]:
    conn->lnk → old_lnk (non-NULL, socket hashed ✓)
    [about to read old_lnk->smcibdev->ibdev->name]

smc_switch_link_and_count() [send_lock held]:
    smcr_link_put(old_lnk) → last ref → __smcr_link_clear()
        memset(old_lnk, 0, ...) ← smcibdev = NULL

diag reader:
    old_lnk->smcibdev->ibdev->name ← NULL deref

The *unhash* invariant says nothing about the link a connection used to
point at before *smc_switch_link_and_count*() swapped it. Hash
membership of the socket provides no protection here because the socket
is still hashed — the link pointer simply changed underneath the diag
reader.

Any ideas on this?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-09-01 15:13       ` Mahanta Jambigi
@ 2026-09-02 12:16         ` Dust Li
  2026-09-03  7:56           ` Mahanta Jambigi
  0 siblings, 1 reply; 10+ messages in thread
From: Dust Li @ 2026-09-02 12:16 UTC (permalink / raw)
  To: Mahanta Jambigi, andrew+netdev, davem, edumazet, kuba, pabeni,
	alibuda, sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma

On 2026-09-01 20:43:11, Mahanta Jambigi wrote:
>
>
>On 01/09/26 6:32 pm, Dust Li wrote:
>> On 2026-08-31 20:27:38, Mahanta Jambigi wrote:
>>>
>>>
>>> On 31/08/26 7:12 pm, Dust Li wrote:
>>>> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>>>>> This series fixes multiple lifetime races in the SMC diag dump path.
>>>>>
>>>>> The first patch adds the basic infrastructure needed to synchronize diag readers
>>>>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>>>>> per-connection spinlock and uses it in the link switch and connection free
>>>>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>>>>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>>>>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>>>>> without borrowing any extra reference.
>>>>>
>>>>> The second patch fixes two races in smc_diag itself:
>>>>>
>>>>> - serialize clcsock field access against smc_clcsock_release() with
>>>>>  mutex_trylock()
>>>>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>>>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>>>>  still registered, then snapshot all required fields and call nla_put()
>>>>>  after releasing the lock
>>>>
>>>> Hi Mahanta,
>>>>
>>>> As discussed in the other thread, I think we should defer the release of
>>>> smc->clcsock and remove clcsock_release_lock.
>>>>
>>>> In that case, we should no longer need these two patches. Also,
>>>> introducing more locks in SMC is the last thing I want to do :)
>>>
>>> Thanks for the new series "[RFC net-next 0/7] net/smc: tie clcsock
>>> lifetime to the smc socket and remove clcsock_release_lock" — once it
>>> lands, we can drop the mutex_trylock() fix for Race 1 (clcsock).
>>>
>>> However, Race 2 remains open. Your series does not touch smc_core.c or
>>> smc_cdc.c, so smc_conn_free(), smc_switch_link_and_count(), and
>>> smc_cdc_msg_validate() still write conn->lgr/conn->lnk with no
>>> synchronization against the diag reader.
>> 
>> Hi Mahanta,
>> 
>> Thanks for the detailed explanation. You are right that a per-connection
>> spinlock can work here, and I agree none of the lock sites are on the
>> per-message hot path. But I think we can also do the same thing we did with
>> clcsock_release_lock: instead of adding a lock, tie the lifetime of
>> conn->lgr/conn->lnk to the point where the connection stops being observable,
>> and remove the need for synchronization altogether.
>> 
>> For lgr/lnk, that point is the hash table. Once the connection is unhashed, the
>> diag dump (which iterates under the hash read_lock) can no longer reach it. So
>> if we make sure the connection-owned references are only dropped after unhash,
>> the invariant becomes: holding the hash read_lock and seeing a non-NULL
>> conn->lgr implies it is safe to dereference. The diag path then reduces to
>> `hold hash read_lock -> read conn->lgr -> if non-NULL, use it -> done` with no
>> new lock, no extra reference, and no trylock. The invariant is carried by
>> object lifetime rather than by a lock, which I find easier to keep correct over
>> time.
>
>I looked carefully at the new design and found one remaining gap.
>
>The *unhash* invariant — "any socket in the hash has its
>connection-owned lgr/lnk refs held" — protects against smc_conn_free()
>dropping refs while the socket is still hashed. However it does not
>protect the conn->lnk->smcibdev->ibdev->name access in the
>SMC_DIAG_LGRINFO block in smc_diag.c file.
>
>The gap is in *smc_switch_link_and_count*(). It is called under
>send_lock (not under any hash-related lock) and calls
>smcr_link_put(conn->lnk) on the old link before reassigning conn->lnk.
>If that put drops the last reference, __smcr_link_clear() runs
>immediately, doing memset(lnk, 0, sizeof(struct smc_link)) which zeroes
>lnk->smcibdev. The socket remains hashed throughout — so the *unhash*
>invariant is not violated — but the diag reader can hold a stale pointer
>to the old link and race this memset. The timeline:
>
>diag reader [hash read_lock held]:
>    conn->lnk → old_lnk (non-NULL, socket hashed ✓)
>    [about to read old_lnk->smcibdev->ibdev->name]
>
>smc_switch_link_and_count() [send_lock held]:
>    smcr_link_put(old_lnk) → last ref → __smcr_link_clear()
>        memset(old_lnk, 0, ...) ← smcibdev = NULL
>
>diag reader:
>    old_lnk->smcibdev->ibdev->name ← NULL deref
>
>The *unhash* invariant says nothing about the link a connection used to
>point at before *smc_switch_link_and_count*() swapped it. Hash
>membership of the socket provides no protection here because the socket
>is still hashed — the link pointer simply changed underneath the diag
>reader.
>
>Any ideas on this?

Good catch ! You are right that the "unhash invariant" as stated
does not cover the stale-conn->lnk race through a link switch + clear.

I think we can close this window by holding the SMC hash table lock in
smcr_link_clear()? As the diag walker captures and dereferences the stale
pointer within a single read_lock hold on the SMC hash table. Since
smc_link_clear() is a cold path, and the region where we hold the SMC hash
table lock is small, the overhead should be negligible.


Something like this:

```diff
diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
index 5af55abece57..ca3030f02e70 100644
--- a/net/smc/smc_core.c
+++ b/net/smc/smc_core.c
@@ -1361,6 +1361,18 @@ static void __smcr_link_clear(struct smc_link *lnk)
        struct smc_ib_device *smcibdev;
 
        smc_wr_free_link_mem(lnk);
 
+       write_lock_bh(&smc_v4_hashinfo.lock);
+       write_lock_bh(&smc_v6_hashinfo.lock);
        smc_ibdev_cnt_dec(lnk);
        put_device(&lnk->smcibdev->ibdev->dev);
        smcibdev = lnk->smcibdev;
@@ -1368,6 +1380,9 @@ static void __smcr_link_clear(struct smc_link *lnk)
        lnk->state = SMC_LNK_UNUSED;
        if (!atomic_dec_return(&smcibdev->lnk_cnt))
                wake_up(&smcibdev->lnks_deleted);
+       write_unlock_bh(&smc_v6_hashinfo.lock);
+       write_unlock_bh(&smc_v4_hashinfo.lock);
```

What do you think ?

Best regards,
Dust

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
  2026-09-02 12:16         ` Dust Li
@ 2026-09-03  7:56           ` Mahanta Jambigi
  0 siblings, 0 replies; 10+ messages in thread
From: Mahanta Jambigi @ 2026-09-03  7:56 UTC (permalink / raw)
  To: dust.li, andrew+netdev, davem, edumazet, kuba, pabeni, alibuda,
	sidraya, hidayath
  Cc: pasic, horms, tonylu, guwen, stable, netdev, linux-s390,
	linux-rdma



On 02/09/26 5:46 pm, Dust Li wrote:
> On 2026-09-01 20:43:11, Mahanta Jambigi wrote:
>>
>>
>> On 01/09/26 6:32 pm, Dust Li wrote:
>>> On 2026-08-31 20:27:38, Mahanta Jambigi wrote:
>>>>
>>>>
>>>> On 31/08/26 7:12 pm, Dust Li wrote:
>>>>> On 2026-08-28 08:54:37, Mahanta Jambigi wrote:
>>>>>> This series fixes multiple lifetime races in the SMC diag dump path.
>>>>>>
>>>>>> The first patch adds the basic infrastructure needed to synchronize diag readers
>>>>>> against connection-owned conn->lgr/conn->lnk updates. It introduces a
>>>>>> per-connection spinlock and uses it in the link switch and connection free
>>>>>> handoff paths. conn->lgr and conn->lnk are NULLed under the lock before the
>>>>>> borrowed references are released, so a non-NULL conn->lgr seen under the lock
>>>>>> guarantees the lgr object is alive. The diag reader can rely on this invariant
>>>>>> without borrowing any extra reference.
>>>>>>
>>>>>> The second patch fixes two races in smc_diag itself:
>>>>>>
>>>>>> - serialize clcsock field access against smc_clcsock_release() with
>>>>>>  mutex_trylock()
>>>>>> - take conn->lgr_lnk_lock when reading conn->lgr and conn->lnk; use
>>>>>>  smc_conn_lgr_valid() inside the lock to check that the connection is
>>>>>>  still registered, then snapshot all required fields and call nla_put()
>>>>>>  after releasing the lock
>>>>>
>>>>> Hi Mahanta,
>>>>>
>>>>> As discussed in the other thread, I think we should defer the release of
>>>>> smc->clcsock and remove clcsock_release_lock.
>>>>>
>>>>> In that case, we should no longer need these two patches. Also,
>>>>> introducing more locks in SMC is the last thing I want to do :)
>>>>
>>>> Thanks for the new series "[RFC net-next 0/7] net/smc: tie clcsock
>>>> lifetime to the smc socket and remove clcsock_release_lock" — once it
>>>> lands, we can drop the mutex_trylock() fix for Race 1 (clcsock).
>>>>
>>>> However, Race 2 remains open. Your series does not touch smc_core.c or
>>>> smc_cdc.c, so smc_conn_free(), smc_switch_link_and_count(), and
>>>> smc_cdc_msg_validate() still write conn->lgr/conn->lnk with no
>>>> synchronization against the diag reader.
>>>
>>> Hi Mahanta,
>>>
>>> Thanks for the detailed explanation. You are right that a per-connection
>>> spinlock can work here, and I agree none of the lock sites are on the
>>> per-message hot path. But I think we can also do the same thing we did with
>>> clcsock_release_lock: instead of adding a lock, tie the lifetime of
>>> conn->lgr/conn->lnk to the point where the connection stops being observable,
>>> and remove the need for synchronization altogether.
>>>
>>> For lgr/lnk, that point is the hash table. Once the connection is unhashed, the
>>> diag dump (which iterates under the hash read_lock) can no longer reach it. So
>>> if we make sure the connection-owned references are only dropped after unhash,
>>> the invariant becomes: holding the hash read_lock and seeing a non-NULL
>>> conn->lgr implies it is safe to dereference. The diag path then reduces to
>>> `hold hash read_lock -> read conn->lgr -> if non-NULL, use it -> done` with no
>>> new lock, no extra reference, and no trylock. The invariant is carried by
>>> object lifetime rather than by a lock, which I find easier to keep correct over
>>> time.
>>
>> I looked carefully at the new design and found one remaining gap.
>>
>> The *unhash* invariant — "any socket in the hash has its
>> connection-owned lgr/lnk refs held" — protects against smc_conn_free()
>> dropping refs while the socket is still hashed. However it does not
>> protect the conn->lnk->smcibdev->ibdev->name access in the
>> SMC_DIAG_LGRINFO block in smc_diag.c file.
>>
>> The gap is in *smc_switch_link_and_count*(). It is called under
>> send_lock (not under any hash-related lock) and calls
>> smcr_link_put(conn->lnk) on the old link before reassigning conn->lnk.
>> If that put drops the last reference, __smcr_link_clear() runs
>> immediately, doing memset(lnk, 0, sizeof(struct smc_link)) which zeroes
>> lnk->smcibdev. The socket remains hashed throughout — so the *unhash*
>> invariant is not violated — but the diag reader can hold a stale pointer
>> to the old link and race this memset. The timeline:
>>
>> diag reader [hash read_lock held]:
>>    conn->lnk → old_lnk (non-NULL, socket hashed ✓)
>>    [about to read old_lnk->smcibdev->ibdev->name]
>>
>> smc_switch_link_and_count() [send_lock held]:
>>    smcr_link_put(old_lnk) → last ref → __smcr_link_clear()
>>        memset(old_lnk, 0, ...) ← smcibdev = NULL
>>
>> diag reader:
>>    old_lnk->smcibdev->ibdev->name ← NULL deref
>>
>> The *unhash* invariant says nothing about the link a connection used to
>> point at before *smc_switch_link_and_count*() swapped it. Hash
>> membership of the socket provides no protection here because the socket
>> is still hashed — the link pointer simply changed underneath the diag
>> reader.
>>
>> Any ideas on this?
> 
> Good catch ! You are right that the "unhash invariant" as stated
> does not cover the stale-conn->lnk race through a link switch + clear.
> 
> I think we can close this window by holding the SMC hash table lock in
> smcr_link_clear()? As the diag walker captures and dereferences the stale
> pointer within a single read_lock hold on the SMC hash table. Since
> smc_link_clear() is a cold path, and the region where we hold the SMC hash
> table lock is small, the overhead should be negligible.
> 
> 
> Something like this:
> 
> ```diff
> diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
> index 5af55abece57..ca3030f02e70 100644
> --- a/net/smc/smc_core.c
> +++ b/net/smc/smc_core.c
> @@ -1361,6 +1361,18 @@ static void __smcr_link_clear(struct smc_link *lnk)
>         struct smc_ib_device *smcibdev;
>  
>         smc_wr_free_link_mem(lnk);
>  
> +       write_lock_bh(&smc_v4_hashinfo.lock);
> +       write_lock_bh(&smc_v6_hashinfo.lock);
>         smc_ibdev_cnt_dec(lnk);
>         put_device(&lnk->smcibdev->ibdev->dev);
>         smcibdev = lnk->smcibdev;
> @@ -1368,6 +1380,9 @@ static void __smcr_link_clear(struct smc_link *lnk)
>         lnk->state = SMC_LNK_UNUSED;
>         if (!atomic_dec_return(&smcibdev->lnk_cnt))
>                 wake_up(&smcibdev->lnks_deleted);
> +       write_unlock_bh(&smc_v6_hashinfo.lock);
> +       write_unlock_bh(&smc_v4_hashinfo.lock);
> ```
> 
> What do you think ?
Hi Dust,

After debugging further, I believe that __smcr_link_clear() cannot be
called from smc_switch_link_and_count() because
refcount_dec_and_test(&lnk->refcnt) is always false on that path — the
initial ref (set in smcr_link_init(), released only in
smcr_link_clear()) is always held while the switch executes. So there is
no UAF from that path. The only consequence is that the diag reader may
observe a stale conn->lnk pointer value — pointing to the old link which
is still fully live — and read the old link's ibport/link_id/ibname from it.

The real UAF is via smc_conn_free(): it drops both the connection-owned
conn->lnk reference via smcr_link_put() and the connection-owned
conn->lgr reference via smc_lgr_put(), while the socket is still visible
in the hash table. For conn->lgr this means kfree(lgr) can race with the
diag reader dereferencing lgr->is_smcd, lgr->role, lgr->list etc. For
conn->lnk this means memset(lnk, 0, ...) in __smcr_link_clear() can race
with the diag reader dereferencing link->smcibdev->ibdev->name. The
current patch tries to fix this using lgr_lnk_lock. Since you suggested
we fix it with *unhash-before-free*, I thought about it and here is my
proposal.

Proposed fix (high level):

Introduce a smc_conn_unhash() helper with a per-connection unhashed flag
that ensures the socket is removed from the hash table exactly once.
Call it at the top of smc_conn_free(), before any lgr/lnk references are
dropped.

This establishes the invariant: any socket still visible to the diag
reader under read_lock(hash->lock) has valid conn->lgr and conn->lnk
pointers. The mutual exclusion between write_lock_bh(hash->lock) inside
smc_conn_unhash() and the diag reader's read_lock(hash->lock) ensures
that by the time smcr_link_put() runs in smc_conn_free(), the socket is
already gone from the hash — the diag reader either completes before the
unhash or never sees the socket at all.

The unhashed flag is needed because smc_conn_free() can be called from
multiple paths (e.g. smc_conn_kill() ahead of __smc_release()), so we
need to guarantee the unhash happens exactly once.

Does this design look reasonable to you?

There is a separate minor issue — smc_switch_link_and_count() updates
conn->lnk under send_lock while the diag reader loads it without any
lock, which can cause stale ibport/link_id/ibname in *smcss -R* during a
failover event. This is a data race but not a safety issue since the
link struct is always live at that point.

This race is very rare in practice — it requires a concurrent smcss -R
dump to hit the exact CPU-cycle window during a conn->lnk pointer write,
which itself only happens during an exceptional link failover event.
Even when it occurs, the effect is transient: one dump may show the old
link's ibport/link_id/ibname, and the next dump will show the correct
values.

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-09-03  7:56 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH net v2 2/2] net/smc: fix races in smc_diag dump path Mahanta Jambigi
2026-08-31 13:42 ` [PATCH net v2 0/2] net/smc: fix diag dump lifetime races Dust Li
2026-08-31 14:08   ` Mahanta Jambigi
2026-08-31 14:57   ` Mahanta Jambigi
2026-09-01 13:02     ` Dust Li
2026-09-01 15:13       ` Mahanta Jambigi
2026-09-02 12:16         ` Dust Li
2026-09-03  7:56           ` Mahanta Jambigi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox