[RFC PATCH] can: bcm: add locking for bcm tx job updates

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

 



Syzbot detected a concurrent write of op->currframe which is modified in
bcm_can_tx() which can be triggerd from user context in bcm_tx_setup()
and from the soft hrtimer callback bcm_tx_timeout_handler().

This patch reorders the code to minimize the potential concurrent code
section in bcm_tx_setup() and bcm_tx_timeout_handler() which modifies
op->currframe, op->count and executes bcm_can_tx(). This minimized code
section is then protected with spin_lock_bh().

The referenced commit in the "Fixes:" tag did not really cause the issue
but is the first commit where the fix could be reasonably backported.

Cc: Dmitry Vyukov <dvyukov@xxxxxxxxxx>
Fixes: bf74aa86e111 ("can: bcm: switch timer to HRTIMER_MODE_SOFT and remove hrtimer_tasklet")
Reported-by: syzbot+e1786f049e71693263bf@xxxxxxxxxxxxxxxxxxxxxxxxx
Signed-off-by: Oliver Hartkopp <socketcan@xxxxxxxxxxxx>
---
 net/can/bcm.c | 87 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 55 insertions(+), 32 deletions(-)

diff --git a/net/can/bcm.c b/net/can/bcm.c
index a962ec2b8ba5..34edd258f086 100644
--- a/net/can/bcm.c
+++ b/net/can/bcm.c
@@ -41,10 +41,11 @@
  */
 
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
+#include <linux/spinlock.h>
 #include <linux/hrtimer.h>
 #include <linux/list.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 #include <linux/uio.h>
@@ -99,10 +100,11 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)
 }
 
 struct bcm_op {
 	struct list_head list;
 	struct rcu_head rcu;
+	spinlock_t tx_setup_lock; /* protect setup/hrtimer access */
 	int ifindex;
 	canid_t can_id;
 	u32 flags;
 	unsigned long frames_abs, frames_filtered;
 	struct bcm_timeval ival1, ival2;
@@ -400,33 +402,41 @@ static void bcm_tx_start_timer(struct bcm_op *op)
 /* bcm_tx_timeout_handler - performs cyclic CAN frame transmissions */
 static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
 {
 	struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
 	struct bcm_msg_head msg_head;
+	bool send_notification = false;
+
+	spin_lock_bh(&op->tx_setup_lock);
 
 	if (op->kt_ival1 && (op->count > 0)) {
 		op->count--;
-		if (!op->count && (op->flags & TX_COUNTEVT)) {
-
-			/* create notification to user */
-			memset(&msg_head, 0, sizeof(msg_head));
-			msg_head.opcode  = TX_EXPIRED;
-			msg_head.flags   = op->flags;
-			msg_head.count   = op->count;
-			msg_head.ival1   = op->ival1;
-			msg_head.ival2   = op->ival2;
-			msg_head.can_id  = op->can_id;
-			msg_head.nframes = 0;
-
-			bcm_send_to_user(op, &msg_head, NULL, 0);
-		}
+		if (!op->count && (op->flags & TX_COUNTEVT))
+			send_notification = true;
+
 		bcm_can_tx(op);
 
 	} else if (op->kt_ival2) {
 		bcm_can_tx(op);
 	}
 
+	spin_unlock_bh(&op->tx_setup_lock);
+
+	if (send_notification) {
+		/* create notification to user */
+		memset(&msg_head, 0, sizeof(msg_head));
+		msg_head.opcode  = TX_EXPIRED;
+		msg_head.flags   = op->flags;
+		msg_head.count   = 0; /* zero at time of check */
+		msg_head.ival1   = op->ival1;
+		msg_head.ival2   = op->ival2;
+		msg_head.can_id  = op->can_id;
+		msg_head.nframes = 0;
+
+		bcm_send_to_user(op, &msg_head, NULL, 0);
+	}
+
 	return bcm_tx_set_expiry(op, &op->timer) ?
 		HRTIMER_RESTART : HRTIMER_NORESTART;
 }
 
 /*
@@ -860,10 +870,12 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
 			int ifindex, struct sock *sk)
 {
 	struct bcm_sock *bo = bcm_sk(sk);
 	struct bcm_op *op;
 	struct canfd_frame *cf;
+	ktime_t setup_ival1 = ktime_set(0, 0);
+	ktime_t setup_ival2 = ktime_set(0, 0);
 	unsigned int i;
 	int err;
 
 	/* we need a real device to send frames */
 	if (!ifindex)
@@ -975,53 +987,64 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
 
 		/* currently unused in tx_ops */
 		hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC,
 			     HRTIMER_MODE_REL_SOFT);
 
+		spin_lock_init(&op->tx_setup_lock);
+
 		/* add this bcm_op to the list of the tx_ops */
 		list_add(&op->list, &bo->tx_ops);
 
 	} /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */
 
-	if (op->nframes != msg_head->nframes) {
-		op->nframes   = msg_head->nframes;
-		/* start multiple frame transmission with index 0 */
-		op->currframe = 0;
-	}
+	if (op->flags & SETTIMER) {
+		setup_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
+		setup_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
 
-	/* check flags */
+		/* disable an active timer due to zero values? */
+		if (!setup_ival1 && !setup_ival2)
+			hrtimer_cancel(&op->timer);
+	}
 
-	if (op->flags & TX_RESET_MULTI_IDX) {
-		/* start multiple frame transmission with index 0 */
-		op->currframe = 0;
+	if (op->flags & STARTTIMER) {
+		hrtimer_cancel(&op->timer);
+		/* spec: send CAN frame when starting timer */
+		op->flags |= TX_ANNOUNCE;
 	}
 
+	spin_lock_bh(&op->tx_setup_lock);
+
 	if (op->flags & SETTIMER) {
 		/* set timer values */
 		op->count = msg_head->count;
 		op->ival1 = msg_head->ival1;
 		op->ival2 = msg_head->ival2;
-		op->kt_ival1 = bcm_timeval_to_ktime(msg_head->ival1);
-		op->kt_ival2 = bcm_timeval_to_ktime(msg_head->ival2);
+		op->kt_ival1 = setup_ival1;
+		op->kt_ival2 = setup_ival2;
+	}
 
-		/* disable an active timer due to zero values? */
-		if (!op->kt_ival1 && !op->kt_ival2)
-			hrtimer_cancel(&op->timer);
+	if (op->nframes != msg_head->nframes) {
+		op->nframes = msg_head->nframes;
+		/* start multiple frame transmission with index 0 */
+		op->currframe = 0;
 	}
 
-	if (op->flags & STARTTIMER) {
-		hrtimer_cancel(&op->timer);
-		/* spec: send CAN frame when starting timer */
-		op->flags |= TX_ANNOUNCE;
+	/* check flags */
+
+	if (op->flags & TX_RESET_MULTI_IDX) {
+		/* start multiple frame transmission with index 0 */
+		op->currframe = 0;
 	}
 
 	if (op->flags & TX_ANNOUNCE) {
 		bcm_can_tx(op);
 		if (op->count)
 			op->count--;
 	}
 
+	spin_unlock_bh(&op->tx_setup_lock);
+
 	if (op->flags & STARTTIMER)
 		bcm_tx_start_timer(op);
 
 	return msg_head->nframes * op->cfsiz + MHSIZ;
 
-- 
2.30.2




[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