Re: [PATCH net v3] net/smc: fix clcsock and lgr/lnk races in smc_diag dump path

Mahanta Jambigi <[email protected]>
Newsgroups dev.linux.lists.sashiko-reviews,org.kernel.vger.linux-s390,org.kernel.vger.netdev
Message-ID <[email protected]>

On 08/08/26 1:46 pm, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> 
> New issues:
> - [High] Dropping the hash lock per-socket and unconditionally restarting the list walk from the head using a logical index introduces O(N^2) list traversal complexity and causes active sockets to be skipped in the diag dump.
> 
> Pre-existing issues:
> - [Critical] The patch attempts to fix a Use-After-Free race on `smc->clcsock` by adding `clcsock_release_lock` in `smc_diag_msg_common_fill()`, but misses a concurrent teardown path in `smc_accept_dequeue()` that frees the socket without acquiring the mutex.
> --
> 
> --- Patch [1]: [PATCH net v3] net/smc: fix clcsock and lgr/lnk races in smc_diag dump path ---
> commit a16d6de2c1851166c340508a037e5292d66a0907
> Author: Mahanta Jambigi <[email protected]>
> 
> net/smc: fix clcsock and lgr/lnk races in smc_diag dump path
> 
> This commit attempts to fix two races in the SMC diag dump path by holding
> clcsock_release_lock in smc_diag_msg_common_fill() and lock_sock() in
> __smc_diag_dump(). It also modifies the dump iteration to pin sockets, drop
> the hash lock, and restart the traversal from the head of the list.
> 
>> diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
>> index bf0beaa23bdb6..72d77360e504f 100644
>> --- a/net/smc/smc_diag.c
>> +++ b/net/smc/smc_diag.c
>> @@ -39,22 +39,34 @@ 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)
>> -		return;
>> -	r->id.idiag_sport = htons(smc->clcsock->sk->sk_num);
>> -	r->id.idiag_dst[0] = smc->clcsock->sk->sk_daddr;
>> +	/*
>> +	 * smc_clcsock_release() sets smc->clcsock = NULL under
>> +	 * clcsock_release_lock before freeing the socket. Hold the same
>> +	 * mutex here to make the NULL check and all field reads atomic
>> +	 * with that writer. mutex_lock() is safe: this function is called
>> +	 * only after the hash spinlock has been dropped by
>> +	 * smc_diag_dump_proto().
>> +	 */
>> +	mutex_lock(&smc->clcsock_release_lock);
>> +	if (smc->clcsock) {
>> +		r->id.idiag_sport = htons(smc->clcsock->sk->sk_num);
> 
> [Severity: Critical]
> This is a pre-existing issue, but does this mutex fully protect against
> concurrent teardowns?
> 
> Looking at smc_accept_dequeue() in net/smc/af_smc.c, if an unaccepted socket
> is closed (SMC_CLOSED), it unhashes it and frees isk->clcsock without holding
> clcsock_release_lock or the child's socket lock:

I'll address this issue separately as this is a pre-existing one.

> 
> net/smc/af_smc.c:smc_accept_dequeue() {
>     ...
>     if (new_sk->sk_state == SMC_CLOSED) {
>         new_sk->sk_prot->unhash(new_sk);
>         if (isk->clcsock) {
>             sock_release(isk->clcsock);
>             isk->clcsock = NULL;
>         }
>     ...
> }
> 
> If the netlink diag dump thread concurrently iterates the hash table, grabs
> the socket reference, and calls smc_diag_msg_common_fill(), could it acquire
> the mutex, see a non-NULL clcsock, and dereference it while it is being freed
> by smc_accept_dequeue()?
> 
> [ ... ]
> 
>> @@ -204,25 +228,43 @@ static int smc_diag_dump_proto(struct proto *prot, struct sk_buff *skb,
>>  	int rc = 0, num = 0;
>>  	struct sock *sk;
>>  
>> -	read_lock(&prot->h.smc_hash->lock);
>>  	head = &prot->h.smc_hash->ht;
>> +restart:
>> +	num = 0;
>> +	read_lock(&prot->h.smc_hash->lock);
>>  	if (hlist_empty(head))
>>  		goto out;
>> -
>>  	sk_for_each(sk, head) {
>>  		if (!net_eq(sock_net(sk), net))
>>  			continue;
>>  		if (num < snum)
>>  			goto next;
>> +		/*
>> +		 * Pin sk before dropping the lock. refcount_inc_not_zero()
>> +		 * skips sockets already past their last reference.
>> +		 * smc_unhash_sk() nulls sk->sk_node.next via sk_del_node_init()
>> +		 * so resuming an interrupted sk_for_each() would terminate
>> +		 * early if a socket is unhashed while the lock is dropped.
>> +		 * Restart from head after each unlock, using snum to skip
>> +		 * already-dumped entries.
>> +		 */
>> +		if (!refcount_inc_not_zero(&sk->sk_refcnt))
>> +			goto next;
>> +		read_unlock(&prot->h.smc_hash->lock);
>> +
>>  		rc = __smc_diag_dump(sk, skb, cb, nlmsg_data(cb->nlh), bc);
>> +		sock_put(sk);
>> +
>>  		if (rc < 0)
>> -			goto out;
>> +			goto out_nolock;
>> +		snum = num + 1;
>> +		goto restart;
> 
> [Severity: High]
> Does restarting the list walk from the head using a logical index (snum)
> introduce an O(N^2) list traversal complexity?
> 
> If any previously seen sockets are concurrently unhashed while the lock is
> dropped, could stepping over (num < snum) elements skip active sockets that
> shifted to earlier positions in the list?

I got couple of reviews from Sashiko AI & I have addressed them here.

Q1: O(N²) complexity?

Yes, this is O(N²) in the worst case — on each restart we skip
already-dumped entries from the head. This is an accepted trade-off: the
same pattern is used in inet_diag and unix_diag. The lock cannot be held
across __smc_diag_dump() since it now takes sleeping locks (mutex_lock,
lock_sock), so drop-and-restart is unavoidable. The skip itself is cheap
(counter comparison only), and the SMC hash is small in practice.

Q2: Can sockets shift to earlier positions and get skipped?

No. SMC uses an hlist where new sockets are always inserted at the head
via hlist_add_head(). Removal does not reorder remaining nodes. So a
socket that existed before an unlock cannot move to an earlier position
— its ordinal index across restarts is stable. A socket inserted during
the unlock will appear at position 0 on the next restart and will be
skipped by num < snum, but that is correct and consistent behaviour —
netlink dumps are not guaranteed to be atomic snapshots.


Sashiko AI review ·
https://sashiko.dev/#/patchset/[email protected]?part=1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.