diff options
author | Dmitry Vyukov <dvyukov@google.com> | 2015-09-29 18:54:58 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2015-09-29 19:08:29 -0400 |
commit | 218c1f76b8b25d6dc9d01443f071cb618e206b0c (patch) | |
tree | ecd48ba4dadb9da65b5ccb3cb86afc8f692b1fdd /drivers/input/serio | |
parent | 22ef28b43f2c70edf5618918a49cbda84795c0a5 (diff) |
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 <dvyukov@google.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input/serio')
-rw-r--r-- | drivers/input/serio/libps2.c | 22 |
1 files changed, 14 insertions, 8 deletions
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) | |||
212 | * time before the ACK arrives. | 212 | * time before the ACK arrives. |
213 | */ | 213 | */ |
214 | if (ps2_sendbyte(ps2dev, command & 0xff, | 214 | if (ps2_sendbyte(ps2dev, command & 0xff, |
215 | command == PS2_CMD_RESET_BAT ? 1000 : 200)) | 215 | command == PS2_CMD_RESET_BAT ? 1000 : 200)) { |
216 | goto out; | 216 | serio_pause_rx(ps2dev->serio); |
217 | goto out_reset_flags; | ||
218 | } | ||
217 | 219 | ||
218 | for (i = 0; i < send; i++) | 220 | for (i = 0; i < send; i++) { |
219 | if (ps2_sendbyte(ps2dev, param[i], 200)) | 221 | if (ps2_sendbyte(ps2dev, param[i], 200)) { |
220 | goto out; | 222 | serio_pause_rx(ps2dev->serio); |
223 | goto out_reset_flags; | ||
224 | } | ||
225 | } | ||
221 | 226 | ||
222 | /* | 227 | /* |
223 | * The reset command takes a long time to execute. | 228 | * 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 | !(ps2dev->flags & PS2_FLAG_CMD), timeout); | 239 | !(ps2dev->flags & PS2_FLAG_CMD), timeout); |
235 | } | 240 | } |
236 | 241 | ||
242 | serio_pause_rx(ps2dev->serio); | ||
243 | |||
237 | if (param) | 244 | if (param) |
238 | for (i = 0; i < receive; i++) | 245 | for (i = 0; i < receive; i++) |
239 | param[i] = ps2dev->cmdbuf[(receive - 1) - i]; | 246 | param[i] = ps2dev->cmdbuf[(receive - 1) - i]; |
240 | 247 | ||
241 | if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) | 248 | if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) |
242 | goto out; | 249 | goto out_reset_flags; |
243 | 250 | ||
244 | rc = 0; | 251 | rc = 0; |
245 | 252 | ||
246 | out: | 253 | out_reset_flags: |
247 | serio_pause_rx(ps2dev->serio); | ||
248 | ps2dev->flags = 0; | 254 | ps2dev->flags = 0; |
249 | serio_continue_rx(ps2dev->serio); | 255 | serio_continue_rx(ps2dev->serio); |
250 | 256 | ||