Input: psmouse - fix data race in __ps2_command
authorDmitry Vyukov <dvyukov@google.com>
Tue, 29 Sep 2015 22:54:58 +0000 (15:54 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Tue, 29 Sep 2015 23:08:29 +0000 (16:08 -0700)
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 <dvyukov@google.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/serio/libps2.c

index 7551699..316f2c8 100644 (file)
@@ -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);