n_tty: Access termios values safely

Use termios_rwsem to guarantee safe access to the termios values.
This is particularly important for N_TTY as changing certain termios
settings alters the mode of operation.

termios_rwsem must be dropped across throttle/unthrottle since
those functions claim the termios_rwsem exclusively (to guarantee
safe access to the termios and for mutual exclusion).

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Peter Hurley 2013-06-15 09:14:24 -04:00 committed by Greg Kroah-Hartman
parent 6a1c0680cf
commit 9356b535fc
1 changed files with 39 additions and 5 deletions

View File

@ -1491,10 +1491,14 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
* canonical mode and don't have a newline yet! * canonical mode and don't have a newline yet!
*/ */
while (1) { while (1) {
int throttled;
tty_set_flow_change(tty, TTY_THROTTLE_SAFE); tty_set_flow_change(tty, TTY_THROTTLE_SAFE);
if (receive_room(tty) >= TTY_THRESHOLD_THROTTLE) if (receive_room(tty) >= TTY_THRESHOLD_THROTTLE)
break; break;
if (!tty_throttle_safe(tty)) up_read(&tty->termios_rwsem);
throttled = tty_throttle_safe(tty);
down_read(&tty->termios_rwsem);
if (!throttled)
break; break;
} }
__tty_set_flow_change(tty, 0); __tty_set_flow_change(tty, 0);
@ -1503,7 +1507,9 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
char *fp, int count) char *fp, int count)
{ {
down_read(&tty->termios_rwsem);
__receive_buf(tty, cp, fp, count); __receive_buf(tty, cp, fp, count);
up_read(&tty->termios_rwsem);
} }
static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
@ -1512,6 +1518,8 @@ static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
struct n_tty_data *ldata = tty->disc_data; struct n_tty_data *ldata = tty->disc_data;
int room; int room;
down_read(&tty->termios_rwsem);
tty->receive_room = room = receive_room(tty); tty->receive_room = room = receive_room(tty);
if (!room) if (!room)
ldata->no_room = 1; ldata->no_room = 1;
@ -1519,6 +1527,8 @@ static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
if (count) if (count)
__receive_buf(tty, cp, fp, count); __receive_buf(tty, cp, fp, count);
up_read(&tty->termios_rwsem);
return count; return count;
} }
@ -1934,6 +1944,8 @@ do_it_again:
if (c < 0) if (c < 0)
return c; return c;
down_read(&tty->termios_rwsem);
minimum = time = 0; minimum = time = 0;
timeout = MAX_SCHEDULE_TIMEOUT; timeout = MAX_SCHEDULE_TIMEOUT;
if (!ldata->icanon) { if (!ldata->icanon) {
@ -1955,11 +1967,15 @@ do_it_again:
* Internal serialization of reads. * Internal serialization of reads.
*/ */
if (file->f_flags & O_NONBLOCK) { if (file->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&ldata->atomic_read_lock)) if (!mutex_trylock(&ldata->atomic_read_lock)) {
up_read(&tty->termios_rwsem);
return -EAGAIN; return -EAGAIN;
}
} else { } else {
if (mutex_lock_interruptible(&ldata->atomic_read_lock)) if (mutex_lock_interruptible(&ldata->atomic_read_lock)) {
up_read(&tty->termios_rwsem);
return -ERESTARTSYS; return -ERESTARTSYS;
}
} }
packet = tty->packet; packet = tty->packet;
@ -2009,7 +2025,11 @@ do_it_again:
break; break;
} }
n_tty_set_room(tty); n_tty_set_room(tty);
up_read(&tty->termios_rwsem);
timeout = schedule_timeout(timeout); timeout = schedule_timeout(timeout);
down_read(&tty->termios_rwsem);
continue; continue;
} }
__set_current_state(TASK_RUNNING); __set_current_state(TASK_RUNNING);
@ -2048,13 +2068,17 @@ do_it_again:
* we won't get any more characters. * we won't get any more characters.
*/ */
while (1) { while (1) {
int unthrottled;
tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
break; break;
if (!tty->count) if (!tty->count)
break; break;
n_tty_set_room(tty); n_tty_set_room(tty);
if (!tty_unthrottle_safe(tty)) up_read(&tty->termios_rwsem);
unthrottled = tty_unthrottle_safe(tty);
down_read(&tty->termios_rwsem);
if (!unthrottled)
break; break;
} }
__tty_set_flow_change(tty, 0); __tty_set_flow_change(tty, 0);
@ -2076,10 +2100,13 @@ do_it_again:
retval = size; retval = size;
if (nr) if (nr)
clear_bit(TTY_PUSH, &tty->flags); clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) } else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) {
up_read(&tty->termios_rwsem);
goto do_it_again; goto do_it_again;
}
n_tty_set_room(tty); n_tty_set_room(tty);
up_read(&tty->termios_rwsem);
return retval; return retval;
} }
@ -2120,6 +2147,8 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
return retval; return retval;
} }
down_read(&tty->termios_rwsem);
/* Write out any echoed characters that are still pending */ /* Write out any echoed characters that are still pending */
process_echoes(tty); process_echoes(tty);
@ -2173,13 +2202,18 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
retval = -EAGAIN; retval = -EAGAIN;
break; break;
} }
up_read(&tty->termios_rwsem);
schedule(); schedule();
down_read(&tty->termios_rwsem);
} }
break_out: break_out:
__set_current_state(TASK_RUNNING); __set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait); remove_wait_queue(&tty->write_wait, &wait);
if (b - buf != nr && tty->fasync) if (b - buf != nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
up_read(&tty->termios_rwsem);
return (b - buf) ? b - buf : retval; return (b - buf) ? b - buf : retval;
} }