From: Mahanta Jambigi <mjambigi@linux.ibm.com>
To: dust.li@linux.alibaba.com, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, alibuda@linux.alibaba.com,
sidraya@linux.ibm.com, hidayath@linux.ibm.com
Cc: pasic@linux.ibm.com, horms@kernel.org, tonylu@linux.alibaba.com,
guwen@linux.alibaba.com, stable@vger.kernel.org,
netdev@vger.kernel.org, linux-s390@vger.kernel.org,
linux-rdma@vger.kernel.org
Subject: Re: [PATCH net v2 0/2] net/smc: fix diag dump lifetime races
Date: Thu, 3 Sep 2026 13:26:30 +0530 [thread overview]
Message-ID: <899816c9-c908-4d64-8f5c-ba35619f78f2@linux.ibm.com> (raw)
In-Reply-To: <apgTu9dRGIQE6tm3@linux.alibaba.com>
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.
next prev parent reply other threads:[~2026-09-03 7:56 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
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-29 6:55 ` sashiko-bot
2026-08-28 6:54 ` [PATCH net v2 2/2] net/smc: fix races in smc_diag dump path Mahanta Jambigi
2026-08-29 6:55 ` sashiko-bot
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 message]
2026-09-04 15:21 ` Dust Li
2026-09-08 16:23 ` Hidayath Khan
2026-09-09 16:07 ` Dust Li
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=899816c9-c908-4d64-8f5c-ba35619f78f2@linux.ibm.com \
--to=mjambigi@linux.ibm.com \
--cc=alibuda@linux.alibaba.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=dust.li@linux.alibaba.com \
--cc=edumazet@google.com \
--cc=guwen@linux.alibaba.com \
--cc=hidayath@linux.ibm.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=linux-s390@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pasic@linux.ibm.com \
--cc=sidraya@linux.ibm.com \
--cc=stable@vger.kernel.org \
--cc=tonylu@linux.alibaba.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.