From: Dmitry Vyukov Date: Tue, 29 Sep 2015 22:54:58 +0000 (-0700) Subject: Input: psmouse - fix data race in __ps2_command X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=218c1f76b8b25d6dc9d01443f071cb618e206b0c;p=openwrt%2Fstaging%2Fblogic.git Input: psmouse - fix data race in __ps2_command The data race happens on ps2dev->cmdcnt and ps2dev->cmdbuf contents. __ps2_command reads that data concurrently with the interrupt handler. As the result, for example, if a response arrives just after the timeout, __ps2_command can copy out garbage from ps2dev->cmdbuf but then see that ps2dev->cmdcnt is 0 and return success. Stop the interrupt handler with serio_pause_rx() before reading the results. The data race was found with KernelThreadSanitizer (KTSAN). Signed-off-by: Dmitry Vyukov Signed-off-by: Dmitry Torokhov --- diff --git a/drivers/input/serio/libps2.c b/drivers/input/serio/libps2.c index 75516996db20..316f2c897101 100644 --- a/drivers/input/serio/libps2.c +++ b/drivers/input/serio/libps2.c @@ -212,12 +212,17 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) * time before the ACK arrives. */ if (ps2_sendbyte(ps2dev, command & 0xff, - command == PS2_CMD_RESET_BAT ? 1000 : 200)) - goto out; + command == PS2_CMD_RESET_BAT ? 1000 : 200)) { + serio_pause_rx(ps2dev->serio); + goto out_reset_flags; + } - for (i = 0; i < send; i++) - if (ps2_sendbyte(ps2dev, param[i], 200)) - goto out; + for (i = 0; i < send; i++) { + if (ps2_sendbyte(ps2dev, param[i], 200)) { + serio_pause_rx(ps2dev->serio); + goto out_reset_flags; + } + } /* * The reset command takes a long time to execute. @@ -234,17 +239,18 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) !(ps2dev->flags & PS2_FLAG_CMD), timeout); } + serio_pause_rx(ps2dev->serio); + if (param) for (i = 0; i < receive; i++) param[i] = ps2dev->cmdbuf[(receive - 1) - i]; if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) - goto out; + goto out_reset_flags; rc = 0; - out: - serio_pause_rx(ps2dev->serio); + out_reset_flags: ps2dev->flags = 0; serio_continue_rx(ps2dev->serio);