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:
parent
fbfe9c847e
commit
f71f94845e
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue