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>
This commit is contained in:
Al Viro 2011-09-14 16:21:25 -07:00 committed by Linus Torvalds
parent fbfe9c847e
commit f71f94845e
2 changed files with 13 additions and 13 deletions

View file

@ -399,8 +399,8 @@ int line_setup_irq(int fd, int input, int output, struct line *line, void *data)
* is done under a spinlock. Checking whether the device is in use is * is done under a spinlock. Checking whether the device is in use is
* line->tty->count > 1, also under the spinlock. * line->tty->count > 1, also under the spinlock.
* *
* tty->count serves to decide whether the device should be enabled or * line->count serves to decide whether the device should be enabled or
* disabled on the host. If it's equal to 1, then we are doing the * disabled on the host. If it's equal to 0, then we are doing the
* first open or last close. Otherwise, open and close just return. * first open or last close. Otherwise, open and close just return.
*/ */
@ -414,16 +414,16 @@ int line_open(struct line *lines, struct tty_struct *tty)
goto out_unlock; goto out_unlock;
err = 0; err = 0;
if (tty->count > 1) if (line->count++)
goto out_unlock; goto out_unlock;
spin_unlock(&line->count_lock); BUG_ON(tty->driver_data);
tty->driver_data = line; tty->driver_data = line;
line->tty = tty; line->tty = tty;
spin_unlock(&line->count_lock);
err = enable_chan(line); err = enable_chan(line);
if (err) if (err) /* line_close() will be called by our caller */
return err; return err;
INIT_DELAYED_WORK(&line->task, line_timer_cb); INIT_DELAYED_WORK(&line->task, line_timer_cb);
@ -436,7 +436,7 @@ int line_open(struct line *lines, struct tty_struct *tty)
chan_window_size(&line->chan_list, &tty->winsize.ws_row, chan_window_size(&line->chan_list, &tty->winsize.ws_row,
&tty->winsize.ws_col); &tty->winsize.ws_col);
return err; return 0;
out_unlock: out_unlock:
spin_unlock(&line->count_lock); spin_unlock(&line->count_lock);
@ -460,17 +460,16 @@ void line_close(struct tty_struct *tty, struct file * filp)
flush_buffer(line); flush_buffer(line);
spin_lock(&line->count_lock); spin_lock(&line->count_lock);
if (!line->valid) BUG_ON(!line->valid);
goto out_unlock;
if (tty->count > 1) if (--line->count)
goto out_unlock; goto out_unlock;
spin_unlock(&line->count_lock);
line->tty = NULL; line->tty = NULL;
tty->driver_data = NULL; tty->driver_data = NULL;
spin_unlock(&line->count_lock);
if (line->sigio) { if (line->sigio) {
unregister_winch(tty); unregister_winch(tty);
line->sigio = 0; line->sigio = 0;
@ -498,7 +497,7 @@ static int setup_one_line(struct line *lines, int n, char *init, int init_prio,
spin_lock(&line->count_lock); spin_lock(&line->count_lock);
if (line->tty != NULL) { if (line->count) {
*error_out = "Device is already open"; *error_out = "Device is already open";
goto out; goto out;
} }

View file

@ -33,6 +33,7 @@ struct line_driver {
struct line { struct line {
struct tty_struct *tty; struct tty_struct *tty;
spinlock_t count_lock; spinlock_t count_lock;
unsigned long count;
int valid; int valid;
char *init_str; char *init_str;