diff options
| author | Al Viro <viro@ftp.linux.org.uk> | 2011-09-14 19:21:25 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-09-14 21:09:37 -0400 |
| commit | f71f94845e0126884eca8ce57a92e30b189c8e71 (patch) | |
| tree | 4284aa9143c6f1b5969da372a81bc75ba7ef4e3e | |
| parent | fbfe9c847edf57ac8232aeafb290f272289893a3 (diff) | |
um: fix oopsable race in line_close()
tty->count is decremented only after ->close() had been called and
several tasks can hit it in parallel. As the result, using tty->count
to check if you are the last one is broken. We end up leaving line->tty
not reset to NULL and the next IRQ on that sucker will blow up trying to
dereference pointers from kfree'd struct tty.
Fix is obvious: we need to use a counter of our own.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Richard Weinberger <richard@nod.at>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | arch/um/drivers/line.c | 25 | ||||
| -rw-r--r-- | arch/um/include/shared/line.h | 1 |
2 files changed, 13 insertions, 13 deletions
diff --git a/arch/um/drivers/line.c b/arch/um/drivers/line.c index d51c404239a8..c5bff1ddeabc 100644 --- a/arch/um/drivers/line.c +++ b/arch/um/drivers/line.c | |||
| @@ -399,8 +399,8 @@ int line_setup_irq(int fd, int input, int output, struct line *line, void *data) | |||
| 399 | * is done under a spinlock. Checking whether the device is in use is | 399 | * is done under a spinlock. Checking whether the device is in use is |
| 400 | * line->tty->count > 1, also under the spinlock. | 400 | * line->tty->count > 1, also under the spinlock. |
| 401 | * | 401 | * |
| 402 | * tty->count serves to decide whether the device should be enabled or | 402 | * line->count serves to decide whether the device should be enabled or |
| 403 | * disabled on the host. If it's equal to 1, then we are doing the | 403 | * disabled on the host. If it's equal to 0, then we are doing the |
| 404 | * first open or last close. Otherwise, open and close just return. | 404 | * first open or last close. Otherwise, open and close just return. |
| 405 | */ | 405 | */ |
| 406 | 406 | ||
| @@ -414,16 +414,16 @@ int line_open(struct line *lines, struct tty_struct *tty) | |||
| 414 | goto out_unlock; | 414 | goto out_unlock; |
| 415 | 415 | ||
| 416 | err = 0; | 416 | err = 0; |
| 417 | if (tty->count > 1) | 417 | if (line->count++) |
| 418 | goto out_unlock; | 418 | goto out_unlock; |
| 419 | 419 | ||
| 420 | spin_unlock(&line->count_lock); | 420 | BUG_ON(tty->driver_data); |
| 421 | |||
| 422 | tty->driver_data = line; | 421 | tty->driver_data = line; |
| 423 | line->tty = tty; | 422 | line->tty = tty; |
| 424 | 423 | ||
| 424 | spin_unlock(&line->count_lock); | ||
| 425 | err = enable_chan(line); | 425 | err = enable_chan(line); |
| 426 | if (err) | 426 | if (err) /* line_close() will be called by our caller */ |
| 427 | return err; | 427 | return err; |
| 428 | 428 | ||
| 429 | INIT_DELAYED_WORK(&line->task, line_timer_cb); | 429 | INIT_DELAYED_WORK(&line->task, line_timer_cb); |
| @@ -436,7 +436,7 @@ int line_open(struct line *lines, struct tty_struct *tty) | |||
| 436 | chan_window_size(&line->chan_list, &tty->winsize.ws_row, | 436 | chan_window_size(&line->chan_list, &tty->winsize.ws_row, |
| 437 | &tty->winsize.ws_col); | 437 | &tty->winsize.ws_col); |
| 438 | 438 | ||
| 439 | return err; | 439 | return 0; |
| 440 | 440 | ||
| 441 | out_unlock: | 441 | out_unlock: |
| 442 | spin_unlock(&line->count_lock); | 442 | spin_unlock(&line->count_lock); |
| @@ -460,17 +460,16 @@ void line_close(struct tty_struct *tty, struct file * filp) | |||
| 460 | flush_buffer(line); | 460 | flush_buffer(line); |
| 461 | 461 | ||
| 462 | spin_lock(&line->count_lock); | 462 | spin_lock(&line->count_lock); |
| 463 | if (!line->valid) | 463 | BUG_ON(!line->valid); |
| 464 | goto out_unlock; | ||
| 465 | 464 | ||
| 466 | if (tty->count > 1) | 465 | if (--line->count) |
| 467 | goto out_unlock; | 466 | goto out_unlock; |
| 468 | 467 | ||
| 469 | spin_unlock(&line->count_lock); | ||
| 470 | |||
| 471 | line->tty = NULL; | 468 | line->tty = NULL; |
| 472 | tty->driver_data = NULL; | 469 | tty->driver_data = NULL; |
| 473 | 470 | ||
| 471 | spin_unlock(&line->count_lock); | ||
| 472 | |||
| 474 | if (line->sigio) { | 473 | if (line->sigio) { |
| 475 | unregister_winch(tty); | 474 | unregister_winch(tty); |
| 476 | line->sigio = 0; | 475 | line->sigio = 0; |
| @@ -498,7 +497,7 @@ static int setup_one_line(struct line *lines, int n, char *init, int init_prio, | |||
| 498 | 497 | ||
| 499 | spin_lock(&line->count_lock); | 498 | spin_lock(&line->count_lock); |
| 500 | 499 | ||
| 501 | if (line->tty != NULL) { | 500 | if (line->count) { |
| 502 | *error_out = "Device is already open"; | 501 | *error_out = "Device is already open"; |
| 503 | goto out; | 502 | goto out; |
| 504 | } | 503 | } |
diff --git a/arch/um/include/shared/line.h b/arch/um/include/shared/line.h index 72f4f25af247..63df3ca02ac2 100644 --- a/arch/um/include/shared/line.h +++ b/arch/um/include/shared/line.h | |||
| @@ -33,6 +33,7 @@ struct line_driver { | |||
| 33 | struct line { | 33 | struct line { |
| 34 | struct tty_struct *tty; | 34 | struct tty_struct *tty; |
| 35 | spinlock_t count_lock; | 35 | spinlock_t count_lock; |
| 36 | unsigned long count; | ||
| 36 | int valid; | 37 | int valid; |
| 37 | 38 | ||
| 38 | char *init_str; | 39 | char *init_str; |
