Netdev List
 help / color / mirror / Atom feed
From: Mahanta Jambigi <mjambigi@linux.ibm.com>
To: netdev-bot+sashiko@kernel.org
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, alibuda@linux.alibaba.com,
	dust.li@linux.alibaba.com, sidraya@linux.ibm.com,
	hidayath@linux.ibm.com, 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 v13] net/smc: fix lgr/lnk lifetime vs diag reader race
Date: Wed, 28 Oct 2026 12:09:44 +0530	[thread overview]
Message-ID: <3190b92c-2fdc-41da-b0e2-b8a6945bef64@linux.ibm.com> (raw)
In-Reply-To: <179311414480.3134.15723255171112798704@kernel.org>



On 27/10/26 8:45 pm, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 1 · Medium: 1 · 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).
> 
> - [High] Use-after-free read of the link group in __smc_diag_dump()
>   (net/smc/smc_diag.c, lines 93-94) for hashed SMC_CLOSED sockets that…
> - [Medium] The new state-based guard in __smc_diag_dump() (smc_diag.c:93
>   and 107-109) can be bypassed by a stale-registration socket that…
> 
> --
> 
> net/smc: fix lgr/lnk lifetime vs diag reader race
> 
> This patch tries to close several windows where the SMC sock_diag dump
> can dereference a freed or partially freed conn/lgr/lnk on a socket that
> is still hashed. It adds an SMC_INIT/SMC_CLOSED/fallback guard in
> __smc_diag_dump(), pairs it with acquire/release on sk_state, and unhashes
> sockets before smc_conn_free() and smcd_buf_detach() in the teardown
> paths. It also uses the cached link->ibname for the LGRINFO attribute.
> 
> [ ... ]
> 
>> diff --git a/net/smc/smc_diag.c b/net/smc/smc_diag.c
>> index bf0beaa23bdb6..84c45aabc196c 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)
> 
> [Severity: High]
> Can this still dereference a freed link group for a hashed socket in
> SMC_CLOSED?
> 
> The diag_mode check here only excludes SMC_INIT. The new SMC_INIT /
> SMC_CLOSED / use_fallback guard comes later, after
> smc->conn.lgr->is_smcd has already been loaded.
> 
> The v13 changelog describes how a stale alert_token_local can survive.
> Once smc_lgr_terminate_sched() has emptied lgr->list, smc_conn_free()
> skips the unregister but still drops the references:
> 
> net/smc/smc_core.c:smc_conn_free() {
>     ...
> 	if (!list_empty(&lgr->list)) {
> 		smc_buf_unuse(conn, lgr); /* allow buffer reuse */
> 		smc_lgr_unregister_conn(conn);
> 	}
>     ...
> lgr_put:
> 	if (!lgr->is_smcd)
> 		smcr_link_put(conn->lnk); /* link_hold in smc_conn_create() */
> 	smc_lgr_put(lgr); /* lgr_hold in smc_conn_create() */
> }
> 
> On the server side this looks reachable as:
> 
> smc_listen_work()
>   smc_conn_create()          <- joins existing SMC-R lgr L
>                              <- smc_lgr_terminate_sched(L) empties L->list
>   smc_clc_wait_msg()         <- peer resets, rc < 0 (-ECONNRESET/-EPROTO)
>   smc_listen_decline()
>     smc_conn_abort()
>       smc_conn_free()        <- unregister skipped, token stays set
>     smc_listen_out_err()     <- SMC_CLOSED, use_fallback false, hashed
> 
> Since rc < 0 there is no fallback. The socket stays hashed in the accept
> queue or until smc_close_non_accepted() takes the sock lock. Later,
> terminate_work runs:
> 
> __smc_lgr_terminate()
>   smc_conn_kill()            <- clears token under conns_lock
>     smc_close_active_abort() <- SMC_CLOSED case just breaks, no unhash
>   smc_lgr_cleanup()
>   smc_lgr_free()
>     smc_lgr_put()
>       __smc_lgr_free()       <- kfree(lgr)
> 
> Nothing in that sequence synchronizes with the hash read_lock that the
> diag reader holds. A reader could see the non-zero token in
> smc_conn_lgr_valid() and then load smc->conn.lgr->is_smcd after the
> kfree. That would read freed memory.
> 
> The "Fourth" paragraph of the commit message says this case must not
> happen: observing SMC_CLOSED and dereferencing an lgr whose reference was
> already dropped. The smp_store_release() added in smc_listen_out_err()
> only helps when the token was actually cleared, and here it was not.
> 
> The client close() path (SMC_INIT -> SMC_CLOSED in smc_close_active())
> doesn't seem to be exploitable this way. The reader's read_lock blocks
> the unhash in __smc_release() while the sock lock is held. The server
> path above has no such protection.
> 
> Could the SMC_INIT / SMC_CLOSED / use_fallback check move ahead of the
> diag_mode computation? Adding r->diag_state != SMC_CLOSED to this
> condition would also work.

Valid finding, fixed in v14.

The v13 diag_mode check only excluded SMC_INIT, so a hashed SMC_CLOSED
socket still loaded smc->conn.lgr->is_smcd. The scenario is reachable.
If smc_lgr_terminate_sched() empties lgr->list after smc_conn_create(),
then smc_conn_free() skips the unregister and leaves alert_token_local
set. A negative rc from smc_clc_wait_msg() then reaches
smc_listen_out_err() with no fallback. The socket stays hashed in
SMC_CLOSED, and terminate_work can free the lgr.

You are also right that smp_store_release() doesn't help here, since the
token was never cleared. v14 excludes SMC_CLOSED before conn->lgr is
read in __smc_diag_dump(), and the commit message now says what the
release store does and doesn't order.

The stale token itself is not new. It comes from smc_conn_free()
skipping the unregister when lgr->list is empty (2a0674fffb6b).

> 
>>  		r->diag_mode = SMC_DIAG_MODE_SMCD;
>>  	else
>>  		r->diag_mode = SMC_DIAG_MODE_SMCR;
>> @@ -103,6 +104,10 @@ 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 ||
>> +	    smc->use_fallback)
>> +		goto out;
>> +
> 
> [Severity: Medium]
> Can a stale-registration socket get past this guard if it leaves
> SMC_INIT through passive close instead of fallback?
> 
> Here is a scenario on an SMC-R client:
> 
> smc_connect_rdma()
>   smc_conn_create()             <- registers conn in existing lgr L
>                                 <- smc_lgr_terminate_sched(L) empties
>                                    L->list
>   smc_clc_send_confirm() fails  <- rc < 0
>   smc_conn_abort()
>     smc_conn_free()             <- unregister skipped, lgr/lnk refs
>                                    dropped
> smc_connect_decline_fallback()  <- rc < 0, returns without fallback
> 
> A positive decline code followed by a failing smc_clc_send_decline()
> ends up in the same place.
> 
> The socket stays hashed in SMC_INIT with use_fallback false. The conn is
> still in L->conns_all with its alert_token_local set, so
> smc_cdc_rx_handler() -> smc_lgr_find_conn() still finds it. The peer has
> the token from the confirm, or can guess it from the sequential counter.
> 
> A CDC with close or abort flags then queues close_work, which moves the
> socket out of SMC_INIT:
> 
> smc_close_passive_work():
> 	case SMC_INIT:
> 		sk->sk_state = SMC_APPCLOSEWAIT1;
> 
> smc_close_passive_abort_received():
> 	case SMC_INIT:
> 	case SMC_ACTIVE:
> 	case SMC_APPCLOSEWAIT1:
> 		sk->sk_state = SMC_PROCESSABORT;
> 
> At that point the state is neither SMC_INIT nor SMC_CLOSED, use_fallback
> is false, and smc_conn_lgr_valid() is true because of the stale token.
> The reader then dereferences:
> 
> - smc->conn.lgr->is_smcd
> - conn->sndbuf_desc->len and conn->rmb_desc->len (still on L's lists,
>   since smc_buf_unuse() was skipped)
> - lgr->list and lgr->role
> - the conn->lnk fields
> 
> Meanwhile __smc_lgr_terminate() -> smc_conn_kill() clears the token and
> calls smc_close_active_abort():
> 
> - The ACTIVE/APPCLOSEWAIT branch never unhashes.
> - The PROCESSABORT branch now unhashes, but smc_conn_free() returns early
>   because conn->freed is already set.
> 
> smc_lgr_free() -> __smc_lgr_free() then frees the buffers and kfree()s L.
> 
> What makes that free wait for a diag reader that has already passed the
> token check? The v13 changelog names this stale-registration case, but it
> seems to handle only the variant where the socket falls back.
Its a pre-existing issue & I'm not going to address this in this patch.

It is *not* a regression. The stale token comes from the same old
smc_conn_free() behaviour (2a0674fffb6b), and before this patch the
reader was exposed in SMC_INIT with no further action needed. The
SMC_INIT guard makes this path harder to hit.

It also needs a misbehaving peer. The socket only leaves SMC_INIT if the
peer sends, or guesses, a CDC with a close or abort flag for the stale
token. A well-behaved peer never does that for a connection it saw fail.
On top of that, a diag reader holding the hash read_lock would have to
stall through smc_conn_kill(), smc_lgr_cleanup() and smc_lgr_free()
after passing the token check. The odds of hitting that are very low. A
peer that can forge CDCs on an established SMC-R link can already abort
connections on that lgr, so it has far simpler options than this race.

The proper fix is to clear the token in smc_conn_free() when lgr->list
is empty. That changes lgr lifetime handling, which is where v11 and v12
got stuck, so it belongs in a separate patch. I'm keeping this one
limited to the diag reader.

pw-bot: cr

      reply	other threads:[~2026-10-28  6:40 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23 15:03 [PATCH net v13] net/smc: fix lgr/lnk lifetime vs diag reader race Mahanta Jambigi
2026-10-23 15:06 ` netdev-bot+sinfo
2026-10-23 17:18   ` Mahanta Jambigi
2026-10-27 15:15 ` netdev-bot+sashiko
2026-10-28  6:39   ` Mahanta Jambigi [this message]

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=3190b92c-2fdc-41da-b0e2-b8a6945bef64@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-bot+sashiko@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox