Re: [PATCH v10 3/3] can: isotp: serialize TX state transitions under so->rx_lock

Oliver Hartkopp <[email protected]>
Newsgroups org.kernel.vger.linux-can,dev.linux.lists.sashiko-reviews
Message-ID <[email protected]>

On 12.07.26 20:11, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Lockless state transition in the timeout handler creates a race condition that causes timeout errors to be silently dropped or misattributed, leading sendmsg() to erroneously return success for timed-out transfers.

The patch set fixes potential UAF and stalls.

The error return codes especially for (uncommon) multi-threaded users 
have been there for years and might be fixed in later patches.

So no reason to delay this patch set.

Best regards,
Oliver

> --
> 
> --- Patch [3]: [PATCH v10 3/3] can: isotp: serialize TX state transitions under so->rx_lock ---
> commit e21ce85bab56b1b57ffd11ce2b23b760bfb72c21
> Author: Oliver Hartkopp <[email protected]>
> 
> can: isotp: serialize TX state transitions under so->rx_lock
> 
> This commit serializes CAN ISOTP TX state transitions and timer cancellations
> under so->rx_lock to prevent concurrent modifications. It also changes the
> timeout handlers to locklessly cancel transfers by checking and updating
> so->tx_gen and so->tx.state.
> 
> Link: https://lore.kernel.org/linux-can/[email protected]/
> 
>> diff --git a/net/can/isotp.c b/net/can/isotp.c
>> index 44c044eb83e12..54becaf6898f1 100644
>> --- a/net/can/isotp.c
>> +++ b/net/can/isotp.c
> [ ... ]
>> @@ -893,53 +927,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data)
> [ ... ]
>> +static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so)
>>   {
>> -	struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
>> -					     txtimer);
>>   	struct sock *sk = &so->sk;
>> +	u32 gen = READ_ONCE(so->tx_gen);
>> +	u32 old_state = READ_ONCE(so->tx.state);
>>   
>>   	/* don't handle timeouts in IDLE or SHUTDOWN state */
>> -	if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN)
>> +	if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN)
>> +		return HRTIMER_NORESTART;
>> +
>> +	/* only claim the timeout if the state is still unchanged */
>> +	if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state)
>>   		return HRTIMER_NORESTART;
>>   
>>   	/* we did not get any flow control or echo frame in time */
>>   
>> -	/* report 'communication error on send' */
>> -	sk->sk_err = ECOMM;
>> -	if (!sock_flag(sk, SOCK_DEAD))
>> -		sk_error_report(sk);
>> +	if (READ_ONCE(so->tx_gen) == gen) {
>> +		/* report 'communication error on send' */
>> +		sk->sk_err = ECOMM;
>> +		if (!sock_flag(sk, SOCK_DEAD))
>> +			sk_error_report(sk);
>> +	}
> 
> [Severity: High]
> Does this lockless state transition create a race window where timeout
> errors are lost?
> 
> By setting so->tx.state to ISOTP_IDLE before setting sk->sk_err = ECOMM,
> a thread waiting in isotp_sendmsg() for wait_tx_done might wake up, observe
> ISOTP_IDLE, and read sock_error() before the error is set, falsely returning
> success.
> 
> Additionally, could another concurrent sendmsg() see the IDLE state,
> claim the socket, and increment tx_gen before the error is set?
> 
> If this happens, the check if (READ_ONCE(so->tx_gen) == gen) will fail,
> and the error will never be reported to the original waiting thread.
> 
> [ ... ]
>> @@ -960,13 +1024,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
> [ ... ]
>> +	/* new transfer: bump so->tx_gen and drain the old one's timers,
>> +	 * still under the so->rx_lock we just claimed the socket with
>> +	 */
>> +	WRITE_ONCE(so->tx.state, ISOTP_SENDING);
>> +	WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1);
>> +	hrtimer_cancel(&so->txtimer);
>> +	hrtimer_cancel(&so->echotimer);
> 
> [Severity: High]
> When this tx_gen increment occurs concurrently with isotp_tx_timeout()
> after the timeout has already transitioned the state to ISOTP_IDLE, doesn't it
> cause the timeout handler to suppress the error for the previous transfer?
> 
> [ ... ]
>
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.