Commit 218c1f76 authored by Dmitry Vyukov's avatar Dmitry Vyukov Committed by Dmitry Torokhov

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: default avatarDmitry Vyukov <dvyukov@google.com>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent 22ef28b4
...@@ -212,12 +212,17 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) ...@@ -212,12 +212,17 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command)
* time before the ACK arrives. * time before the ACK arrives.
*/ */
if (ps2_sendbyte(ps2dev, command & 0xff, if (ps2_sendbyte(ps2dev, command & 0xff,
command == PS2_CMD_RESET_BAT ? 1000 : 200)) command == PS2_CMD_RESET_BAT ? 1000 : 200)) {
goto out; serio_pause_rx(ps2dev->serio);
goto out_reset_flags;
}
for (i = 0; i < send; i++) for (i = 0; i < send; i++) {
if (ps2_sendbyte(ps2dev, param[i], 200)) if (ps2_sendbyte(ps2dev, param[i], 200)) {
goto out; serio_pause_rx(ps2dev->serio);
goto out_reset_flags;
}
}
/* /*
* The reset command takes a long time to execute. * The reset command takes a long time to execute.
...@@ -234,17 +239,18 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) ...@@ -234,17 +239,18 @@ int __ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command)
!(ps2dev->flags & PS2_FLAG_CMD), timeout); !(ps2dev->flags & PS2_FLAG_CMD), timeout);
} }
serio_pause_rx(ps2dev->serio);
if (param) if (param)
for (i = 0; i < receive; i++) for (i = 0; i < receive; i++)
param[i] = ps2dev->cmdbuf[(receive - 1) - i]; param[i] = ps2dev->cmdbuf[(receive - 1) - i];
if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1))
goto out; goto out_reset_flags;
rc = 0; rc = 0;
out: out_reset_flags:
serio_pause_rx(ps2dev->serio);
ps2dev->flags = 0; ps2dev->flags = 0;
serio_continue_rx(ps2dev->serio); serio_continue_rx(ps2dev->serio);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment