Linux CAN drivers development
 help / color / mirror / Atom feed
* [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()
@ 2023-03-31 10:21 Oliver Hartkopp
  2023-03-31 12:06 ` Dae R. Jeong
  0 siblings, 1 reply; 5+ messages in thread
From: Oliver Hartkopp @ 2023-03-31 10:21 UTC (permalink / raw)
  To: linux-can; +Cc: Oliver Hartkopp, Dae R . Jeong, Hillf Danton

As discussed with Dae R. Jeong and Hillf Danton here [1] the sendmsg()
function in isotp.c might get into a race condition when restoring the
former tx.state from the old_state. This patch removes the old_state
concept and implements a proper locking for ISOTP_IDLE transitions in
isotp_sendmsg() inspired by a simplification idea from Hillf Danton.
Additionally a new tx.state ISOTP_SHUTDOWN has been introduced to use
the same locking mechanism from isotp_release() which resolves a
potential race between isotp_sendsmg() and isotp_release().

[1] https://lore.kernel.org/linux-can/ZB%2F93xJxq%2FBUqAgG@dragonet/

Cc: Dae R. Jeong <threeearcat@gmail.com>
Cc: Hillf Danton <hdanton@sina.com>
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
---
 net/can/isotp.c | 49 ++++++++++++++++++++++++++-----------------------
 1 file changed, 26 insertions(+), 23 deletions(-)

diff --git a/net/can/isotp.c b/net/can/isotp.c
index 9bc344851704..2ba043f5a9cb 100644
--- a/net/can/isotp.c
+++ b/net/can/isotp.c
@@ -117,11 +117,12 @@ MODULE_ALIAS("can-proto-6");
 enum {
 	ISOTP_IDLE = 0,
 	ISOTP_WAIT_FIRST_FC,
 	ISOTP_WAIT_FC,
 	ISOTP_WAIT_DATA,
-	ISOTP_SENDING
+	ISOTP_SENDING,
+	ISOTP_SHUTDOWN,
 };
 
 struct tpcon {
 	unsigned int idx;
 	unsigned int len;
@@ -878,12 +879,12 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
 {
 	struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
 					     txtimer);
 	struct sock *sk = &so->sk;
 
-	/* don't handle timeouts in IDLE state */
-	if (so->tx.state == ISOTP_IDLE)
+	/* don't handle timeouts in IDLE or SHUTDOWN state */
+	if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN)
 		return HRTIMER_NORESTART;
 
 	/* we did not get any flow control or echo frame in time */
 
 	/* report 'communication error on send' */
@@ -916,37 +917,37 @@ static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer)
 
 static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 {
 	struct sock *sk = sock->sk;
 	struct isotp_sock *so = isotp_sk(sk);
-	u32 old_state = so->tx.state;
 	struct sk_buff *skb;
 	struct net_device *dev;
 	struct canfd_frame *cf;
 	int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0;
 	int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0;
 	s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT;
 	int off;
 	int err;
 
-	if (!so->bound)
+	if (!so->bound || so->tx.state == ISOTP_SHUTDOWN)
 		return -EADDRNOTAVAIL;
 
+wait_free_buffer:
 	/* we do not support multiple buffers - for now */
-	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE ||
-	    wq_has_sleeper(&so->wait)) {
-		if (msg->msg_flags & MSG_DONTWAIT) {
-			err = -EAGAIN;
-			goto err_out;
-		}
+	if (wq_has_sleeper(&so->wait) && (msg->msg_flags & MSG_DONTWAIT))
+		return -EAGAIN;
 
-		/* wait for complete transmission of current pdu */
-		err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
-		if (err)
-			goto err_out;
+	/* wait for complete transmission of current pdu */
+	err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
+	if (err)
+		return err;
 
-		so->tx.state = ISOTP_SENDING;
+	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) {
+		if (so->tx.state == ISOTP_SHUTDOWN)
+			return -EADDRNOTAVAIL;
+
+		goto wait_free_buffer;
 	}
 
 	if (!size || size > MAX_MSG_LENGTH) {
 		err = -EINVAL;
 		goto err_out_drop;
@@ -1072,25 +1073,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
 		goto err_out_drop;
 	}
 
 	if (wait_tx_done) {
 		/* wait for complete transmission of current pdu */
-		wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
+		err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
+		if (err)
+			return err;
 
 		if (sk->sk_err)
 			return -sk->sk_err;
 	}
 
 	return size;
 
 err_out_drop:
 	/* drop this PDU and unlock a potential wait queue */
-	old_state = ISOTP_IDLE;
-err_out:
-	so->tx.state = old_state;
-	if (so->tx.state == ISOTP_IDLE)
-		wake_up_interruptible(&so->wait);
+	so->tx.state = ISOTP_IDLE;
+	wake_up_interruptible(&so->wait);
 
 	return err;
 }
 
 static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
@@ -1147,15 +1147,18 @@ static int isotp_release(struct socket *sock)
 		return 0;
 
 	so = isotp_sk(sk);
 	net = sock_net(sk);
 
+wait_transmission_finish:
 	/* wait for complete transmission of current pdu */
 	wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
 
+	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE)
+		goto wait_transmission_finish;
+
 	/* force state machines to be idle also when a signal occurred */
-	so->tx.state = ISOTP_IDLE;
 	so->rx.state = ISOTP_IDLE;
 
 	spin_lock(&isotp_notifier_lock);
 	while (isotp_busy_notifier == so) {
 		spin_unlock(&isotp_notifier_lock);
-- 
2.30.2


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

* Re: [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()
  2023-03-31 10:21 [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release() Oliver Hartkopp
@ 2023-03-31 12:06 ` Dae R. Jeong
  2023-03-31 12:21   ` Oliver Hartkopp
  0 siblings, 1 reply; 5+ messages in thread
From: Dae R. Jeong @ 2023-03-31 12:06 UTC (permalink / raw)
  To: Oliver Hartkopp; +Cc: linux-can, Hillf Danton

On Fri, Mar 31, 2023 at 12:21:14PM +0200, Oliver Hartkopp wrote:
> As discussed with Dae R. Jeong and Hillf Danton here [1] the sendmsg()
> function in isotp.c might get into a race condition when restoring the
> former tx.state from the old_state. This patch removes the old_state
> concept and implements a proper locking for ISOTP_IDLE transitions in
> isotp_sendmsg() inspired by a simplification idea from Hillf Danton.
> Additionally a new tx.state ISOTP_SHUTDOWN has been introduced to use
> the same locking mechanism from isotp_release() which resolves a
> potential race between isotp_sendsmg() and isotp_release().
> 
> [1] https://lore.kernel.org/linux-can/ZB%2F93xJxq%2FBUqAgG@dragonet/
> 
> Cc: Dae R. Jeong <threeearcat@gmail.com>
> Cc: Hillf Danton <hdanton@sina.com>
> Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
> ---
>  net/can/isotp.c | 49 ++++++++++++++++++++++++++-----------------------
>  1 file changed, 26 insertions(+), 23 deletions(-)
> 
> diff --git a/net/can/isotp.c b/net/can/isotp.c
> index 9bc344851704..2ba043f5a9cb 100644
> --- a/net/can/isotp.c
> +++ b/net/can/isotp.c
> @@ -117,11 +117,12 @@ MODULE_ALIAS("can-proto-6");
>  enum {
>  	ISOTP_IDLE = 0,
>  	ISOTP_WAIT_FIRST_FC,
>  	ISOTP_WAIT_FC,
>  	ISOTP_WAIT_DATA,
> -	ISOTP_SENDING
> +	ISOTP_SENDING,
> +	ISOTP_SHUTDOWN,
>  };
>  
>  struct tpcon {
>  	unsigned int idx;
>  	unsigned int len;
> @@ -878,12 +879,12 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
>  {
>  	struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
>  					     txtimer);
>  	struct sock *sk = &so->sk;
>  
> -	/* don't handle timeouts in IDLE state */
> -	if (so->tx.state == ISOTP_IDLE)
> +	/* don't handle timeouts in IDLE or SHUTDOWN state */
> +	if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN)
>  		return HRTIMER_NORESTART;
>  
>  	/* we did not get any flow control or echo frame in time */
>  
>  	/* report 'communication error on send' */
> @@ -916,37 +917,37 @@ static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer)
>  
>  static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
>  {
>  	struct sock *sk = sock->sk;
>  	struct isotp_sock *so = isotp_sk(sk);
> -	u32 old_state = so->tx.state;
>  	struct sk_buff *skb;
>  	struct net_device *dev;
>  	struct canfd_frame *cf;
>  	int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0;
>  	int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0;
>  	s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT;
>  	int off;
>  	int err;
>  
> -	if (!so->bound)
> +	if (!so->bound || so->tx.state == ISOTP_SHUTDOWN)
>  		return -EADDRNOTAVAIL;
>  
> +wait_free_buffer:
>  	/* we do not support multiple buffers - for now */
> -	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE ||
> -	    wq_has_sleeper(&so->wait)) {
> -		if (msg->msg_flags & MSG_DONTWAIT) {
> -			err = -EAGAIN;
> -			goto err_out;
> -		}
> +	if (wq_has_sleeper(&so->wait) && (msg->msg_flags & MSG_DONTWAIT))
> +		return -EAGAIN;
>  
> -		/* wait for complete transmission of current pdu */
> -		err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
> -		if (err)
> -			goto err_out;
> +	/* wait for complete transmission of current pdu */
> +	err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
> +	if (err)
> +		return err;
>  
> -		so->tx.state = ISOTP_SENDING;
> +	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) {
> +		if (so->tx.state == ISOTP_SHUTDOWN)
> +			return -EADDRNOTAVAIL;
> +
> +		goto wait_free_buffer;
>  	}
>  
>  	if (!size || size > MAX_MSG_LENGTH) {
>  		err = -EINVAL;
>  		goto err_out_drop;
> @@ -1072,25 +1073,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
>  		goto err_out_drop;
>  	}
>  
>  	if (wait_tx_done) {
>  		/* wait for complete transmission of current pdu */
> -		wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
> +		err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
> +		if (err)
> +			return err;
>  
>  		if (sk->sk_err)
>  			return -sk->sk_err;
>  	}
>  
>  	return size;
>  
>  err_out_drop:
>  	/* drop this PDU and unlock a potential wait queue */
> -	old_state = ISOTP_IDLE;
> -err_out:
> -	so->tx.state = old_state;
> -	if (so->tx.state == ISOTP_IDLE)
> -		wake_up_interruptible(&so->wait);
> +	so->tx.state = ISOTP_IDLE;
> +	wake_up_interruptible(&so->wait);
>  
>  	return err;
>  }
>  
>  static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
> @@ -1147,15 +1147,18 @@ static int isotp_release(struct socket *sock)
>  		return 0;
>  
>  	so = isotp_sk(sk);
>  	net = sock_net(sk);
>  
> +wait_transmission_finish:
>  	/* wait for complete transmission of current pdu */
>  	wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
>  
> +	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE)
> +		goto wait_transmission_finish;
> +
>  	/* force state machines to be idle also when a signal occurred */
> -	so->tx.state = ISOTP_IDLE;
>  	so->rx.state = ISOTP_IDLE;
>  
>  	spin_lock(&isotp_notifier_lock);
>  	while (isotp_busy_notifier == so) {
>  		spin_unlock(&isotp_notifier_lock);
> -- 
> 2.30.2
> 

All looks good for me. I tried to figure out abnormal thread
interleavings regarding the changes in sendmsg() and release(), but I
couldn't.

Thank you for your hard work!

Best regards,
Dae R. Jeong

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

* Re: [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()
  2023-03-31 12:06 ` Dae R. Jeong
@ 2023-03-31 12:21   ` Oliver Hartkopp
  2023-03-31 13:37     ` Dae R. Jeong
  0 siblings, 1 reply; 5+ messages in thread
From: Oliver Hartkopp @ 2023-03-31 12:21 UTC (permalink / raw)
  To: Dae R. Jeong; +Cc: linux-can, Hillf Danton

Hi Dae,

On 31.03.23 14:06, Dae R. Jeong wrote:
> On Fri, Mar 31, 2023 at 12:21:14PM +0200, Oliver Hartkopp wrote:
>> As discussed with Dae R. Jeong and Hillf Danton here [1] the sendmsg()
>> function in isotp.c might get into a race condition when restoring the
>> former tx.state from the old_state. This patch removes the old_state
>> concept and implements a proper locking for ISOTP_IDLE transitions in
>> isotp_sendmsg() inspired by a simplification idea from Hillf Danton.
>> Additionally a new tx.state ISOTP_SHUTDOWN has been introduced to use
>> the same locking mechanism from isotp_release() which resolves a
>> potential race between isotp_sendsmg() and isotp_release().
>>
>> [1] https://lore.kernel.org/linux-can/ZB%2F93xJxq%2FBUqAgG@dragonet/
>>

(..)

>>   
>> +wait_transmission_finish:
>>   	/* wait for complete transmission of current pdu */
>>   	wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
>>   
>> +	if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE)
>> +		goto wait_transmission_finish;
>> +
>>   	/* force state machines to be idle also when a signal occurred */
>> -	so->tx.state = ISOTP_IDLE;
>>   	so->rx.state = ISOTP_IDLE;
>>   
>>   	spin_lock(&isotp_notifier_lock);
>>   	while (isotp_busy_notifier == so) {
>>   		spin_unlock(&isotp_notifier_lock);
>> -- 
>> 2.30.2
>>
> 
> All looks good for me. I tried to figure out abnormal thread
> interleavings regarding the changes in sendmsg() and release(), but I
> couldn't.

I'm still unsure whether a multi-threaded code could send messages and 
close the socket at the same time. But I think the above change would 
fix a potential race here.

Looking at the code I was wondering if I should also check the return 
value of wait_event_interruptible() in isotp_release() and I changed the 
code which also removes the goto statement:

/* wait for complete transmission of current pdu */
while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 
0 &&
        cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE);

/* force state machines to be idle also when a signal occurred */
so->tx.state = ISOTP_SHUTDOWN;
so->rx.state = ISOTP_IDLE;

When wait_event_interruptible() does not return '0' we can neither rely 
on tx.state to be ISOTP_IDLE nor on anybody else taking care for that.

So I would suggest to continue removing the socket.

> Thank you for your hard work!

Thanks for the review and the request to take an additional look at the 
code!

Best regards,
Oliver

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

* Re: [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()
  2023-03-31 12:21   ` Oliver Hartkopp
@ 2023-03-31 13:37     ` Dae R. Jeong
  2023-03-31 15:26       ` Oliver Hartkopp
  0 siblings, 1 reply; 5+ messages in thread
From: Dae R. Jeong @ 2023-03-31 13:37 UTC (permalink / raw)
  To: Oliver Hartkopp; +Cc: linux-can, Hillf Danton

On Fri, Mar 31, 2023 at 9:23 PM Oliver Hartkopp <socketcan@hartkopp.net> wrote:
>
> Hi Dae,
>
(...)
>
> /* wait for complete transmission of current pdu */
> while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) ==
> 0 &&
>         cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE);

I'm not sure, but your intention in this condition is probably
while (wait_event_interruptible() != 0 || cmpxchg() != ISOTP_IDLE)?
So, release() can get out of the loop only if
wait_event_interruptible() returns 0 and cmpxchg() successes?

> /* force state machines to be idle also when a signal occurred */
> so->tx.state = ISOTP_SHUTDOWN;
> so->rx.state = ISOTP_IDLE;
>
> When wait_event_interruptible() does not return '0' we can neither rely
> on tx.state to be ISOTP_IDLE nor on anybody else taking care for that.
>
> So I would suggest to continue removing the socket.
>
> > Thank you for your hard work!
>
> Thanks for the review and the request to take an additional look at the
> code!
>
> Best regards,
> Oliver

Best regards,
Dae R. Jeong

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

* Re: [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()
  2023-03-31 13:37     ` Dae R. Jeong
@ 2023-03-31 15:26       ` Oliver Hartkopp
  0 siblings, 0 replies; 5+ messages in thread
From: Oliver Hartkopp @ 2023-03-31 15:26 UTC (permalink / raw)
  To: Dae R. Jeong; +Cc: linux-can, Hillf Danton



On 31.03.23 15:37, Dae R. Jeong wrote:
> On Fri, Mar 31, 2023 at 9:23 PM Oliver Hartkopp <socketcan@hartkopp.net> wrote:
>>
>> Hi Dae,
>>
> (...)
>>
>> /* wait for complete transmission of current pdu */
>> while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) ==
>> 0 &&
>>          cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE);
> 
> I'm not sure, but your intention in this condition is probably
> while (wait_event_interruptible() != 0 || cmpxchg() != ISOTP_IDLE)?
> So, release() can get out of the loop only if
> wait_event_interruptible() returns 0 and cmpxchg() successes?

No it is the other way around.

When wait_event_interruptible() returns 0 the state has been properly 
set to ISOTP_IDLE (the good case).

And THEN we try to grab the tx.state to be ISOTP_SHUTDOWN.

The while() statement is left when either the tx.state is ISOTP_SHUTDOWN 
(after it was ISOTP_IDLE) 'OR' when a signal occurred which terminated 
wait_event_interruptible().

In the latter case we do not know the value from tx.state.

Therefore I set the tx.state to ISOTP_IDLE/ISOTP_SHUTDOWN in the V4 
patch after checking each wait_event_interruptible() return value:

https://lore.kernel.org/linux-can/20230331131935.21465-1-socketcan@hartkopp.net/

I think this is the best way to handle the signal interrupt case for 
wait_event_interruptible() ?!?

Best regards,
Oliver

> 
>> /* force state machines to be idle also when a signal occurred */
>> so->tx.state = ISOTP_SHUTDOWN;
>> so->rx.state = ISOTP_IDLE;
>>
>> When wait_event_interruptible() does not return '0' we can neither rely
>> on tx.state to be ISOTP_IDLE nor on anybody else taking care for that.
>>
>> So I would suggest to continue removing the socket.
>>
>>> Thank you for your hard work!
>>
>> Thanks for the review and the request to take an additional look at the
>> code!
>>
>> Best regards,
>> Oliver
> 
> Best regards,
> Dae R. Jeong

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

end of thread, other threads:[~2023-03-31 15:26 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-03-31 10:21 [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release() Oliver Hartkopp
2023-03-31 12:06 ` Dae R. Jeong
2023-03-31 12:21   ` Oliver Hartkopp
2023-03-31 13:37     ` Dae R. Jeong
2023-03-31 15:26       ` Oliver Hartkopp

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