Re: [RFC PATCH] can: isotp: fix race between isotp_sendsmg() and isotp_release()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxx>
> Cc: Hillf Danton <hdanton@xxxxxxxx>
> Signed-off-by: Oliver Hartkopp <socketcan@xxxxxxxxxxxx>
> ---
>  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



[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux