Skip to content

Commit 0517374

Browse files
hartkoppmarckleinebudde
authored andcommitted
can: isotp: fix race between isotp_sendsmg() and isotp_release()
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. Remove the old_state concept and implement proper locking for the ISOTP_IDLE transitions in isotp_sendmsg(), inspired by a simplification idea from Hillf Danton. Introduce a new tx.state ISOTP_SHUTDOWN and 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 v1: https://lore.kernel.org/all/[email protected] v2: https://lore.kernel.org/all/[email protected] take care of signal interrupts for wait_event_interruptible() in isotp_release() v3: https://lore.kernel.org/all/[email protected] take care of signal interrupts for wait_event_interruptible() in isotp_sendmsg() in the wait_tx_done case v4: https://lore.kernel.org/all/[email protected] take care of signal interrupts for wait_event_interruptible() in isotp_sendmsg() in ALL cases Cc: Dae R. Jeong <[email protected]> Cc: Hillf Danton <[email protected]> Signed-off-by: Oliver Hartkopp <[email protected]> Fixes: 4f027cb ("can: isotp: split tx timer into transmission and timeout") Link: https://lore.kernel.org/all/[email protected] Cc: [email protected] [mkl: rephrase commit message] Signed-off-by: Marc Kleine-Budde <[email protected]>
1 parent 79e19fa commit 0517374

File tree

1 file changed

+31
-24
lines changed

1 file changed

+31
-24
lines changed

net/can/isotp.c

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ enum {
119119
ISOTP_WAIT_FIRST_FC,
120120
ISOTP_WAIT_FC,
121121
ISOTP_WAIT_DATA,
122-
ISOTP_SENDING
122+
ISOTP_SENDING,
123+
ISOTP_SHUTDOWN,
123124
};
124125

125126
struct tpcon {
@@ -880,8 +881,8 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
880881
txtimer);
881882
struct sock *sk = &so->sk;
882883

883-
/* don't handle timeouts in IDLE state */
884-
if (so->tx.state == ISOTP_IDLE)
884+
/* don't handle timeouts in IDLE or SHUTDOWN state */
885+
if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN)
885886
return HRTIMER_NORESTART;
886887

887888
/* we did not get any flow control or echo frame in time */
@@ -918,7 +919,6 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
918919
{
919920
struct sock *sk = sock->sk;
920921
struct isotp_sock *so = isotp_sk(sk);
921-
u32 old_state = so->tx.state;
922922
struct sk_buff *skb;
923923
struct net_device *dev;
924924
struct canfd_frame *cf;
@@ -928,23 +928,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
928928
int off;
929929
int err;
930930

931-
if (!so->bound)
931+
if (!so->bound || so->tx.state == ISOTP_SHUTDOWN)
932932
return -EADDRNOTAVAIL;
933933

934+
wait_free_buffer:
934935
/* we do not support multiple buffers - for now */
935-
if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE ||
936-
wq_has_sleeper(&so->wait)) {
937-
if (msg->msg_flags & MSG_DONTWAIT) {
938-
err = -EAGAIN;
939-
goto err_out;
940-
}
936+
if (wq_has_sleeper(&so->wait) && (msg->msg_flags & MSG_DONTWAIT))
937+
return -EAGAIN;
941938

942-
/* wait for complete transmission of current pdu */
943-
err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
944-
if (err)
945-
goto err_out;
939+
/* wait for complete transmission of current pdu */
940+
err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
941+
if (err)
942+
goto err_event_drop;
946943

947-
so->tx.state = ISOTP_SENDING;
944+
if (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) {
945+
if (so->tx.state == ISOTP_SHUTDOWN)
946+
return -EADDRNOTAVAIL;
947+
948+
goto wait_free_buffer;
948949
}
949950

950951
if (!size || size > MAX_MSG_LENGTH) {
@@ -1074,21 +1075,25 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
10741075

10751076
if (wait_tx_done) {
10761077
/* wait for complete transmission of current pdu */
1077-
wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
1078+
err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
1079+
if (err)
1080+
goto err_event_drop;
10781081

10791082
if (sk->sk_err)
10801083
return -sk->sk_err;
10811084
}
10821085

10831086
return size;
10841087

1088+
err_event_drop:
1089+
/* got signal: force tx state machine to be idle */
1090+
so->tx.state = ISOTP_IDLE;
1091+
hrtimer_cancel(&so->txfrtimer);
1092+
hrtimer_cancel(&so->txtimer);
10851093
err_out_drop:
10861094
/* drop this PDU and unlock a potential wait queue */
1087-
old_state = ISOTP_IDLE;
1088-
err_out:
1089-
so->tx.state = old_state;
1090-
if (so->tx.state == ISOTP_IDLE)
1091-
wake_up_interruptible(&so->wait);
1095+
so->tx.state = ISOTP_IDLE;
1096+
wake_up_interruptible(&so->wait);
10921097

10931098
return err;
10941099
}
@@ -1150,10 +1155,12 @@ static int isotp_release(struct socket *sock)
11501155
net = sock_net(sk);
11511156

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

11551162
/* force state machines to be idle also when a signal occurred */
1156-
so->tx.state = ISOTP_IDLE;
1163+
so->tx.state = ISOTP_SHUTDOWN;
11571164
so->rx.state = ISOTP_IDLE;
11581165

11591166
spin_lock(&isotp_notifier_lock);

0 commit comments

Comments
 (0)