From: Greg Kroah-Hartman Date: Sat, 4 Dec 2010 00:00:45 +0000 (-0800) Subject: .33 X-Git-Tag: v2.6.27.57~55 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=15a55f7c71b83b5760237770943fc4145c347845;p=thirdparty%2Fkernel%2Fstable-queue.git .33 --- diff --git a/queue-2.6.33/series b/queue-2.6.33/series index fe0a173af76..4e25c6eab7b 100644 --- a/queue-2.6.33/series +++ b/queue-2.6.33/series @@ -129,3 +129,6 @@ viafb-use-proper-register-for-colour-when-doing-fill-ops.patch ecryptfs-clear-lookup_open-flag-when-creating-lower-file.patch md-raid1-really-fix-recovery-looping-when-single-good-device-fails.patch md-fix-return-value-of-rdev_size_change.patch +tty-prevent-dos-in-the-flush_to_ldisc.patch +tty-restore-tty_ldisc_wait_idle.patch +tty_ldisc-fix-bug-on-hangup.patch diff --git a/queue-2.6.33/tty-prevent-dos-in-the-flush_to_ldisc.patch b/queue-2.6.33/tty-prevent-dos-in-the-flush_to_ldisc.patch new file mode 100644 index 00000000000..0515ce2f24d --- /dev/null +++ b/queue-2.6.33/tty-prevent-dos-in-the-flush_to_ldisc.patch @@ -0,0 +1,107 @@ +From e045fec48970df84647a47930fcf7a22ff7229c0 Mon Sep 17 00:00:00 2001 +From: Jiri Olsa +Date: Mon, 8 Nov 2010 19:01:47 +0100 +Subject: tty: prevent DOS in the flush_to_ldisc + +From: Jiri Olsa + +commit e045fec48970df84647a47930fcf7a22ff7229c0 upstream. + +There's a small window inside the flush_to_ldisc function, +where the tty is unlocked and calling ldisc's receive_buf +function. If in this window new buffer is added to the tty, +the processing might never leave the flush_to_ldisc function. + +This scenario will hog the cpu, causing other tty processing +starving, and making it impossible to interface the computer +via tty. + +I was able to exploit this via pty interface by sending only +control characters to the master input, causing the flush_to_ldisc +to be scheduled, but never actually generate any output. + +To reproduce, please run multiple instances of following code. + +- SNIP +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int i, slave, master = getpt(); + char buf[8192]; + + sprintf(buf, "%s", ptsname(master)); + grantpt(master); + unlockpt(master); + + slave = open(buf, O_RDWR); + if (slave < 0) { + perror("open slave failed"); + return 1; + } + + for(i = 0; i < sizeof(buf); i++) + buf[i] = rand() % 32; + + while(1) { + write(master, buf, sizeof(buf)); + } + + return 0; +} +- SNIP + +The attached patch (based on -next tree) fixes this by checking on the +tty buffer tail. Once it's reached, the current work is rescheduled +and another could run. + +Signed-off-by: Jiri Olsa +Acked-by: Alan Cox +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/char/tty_buffer.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +--- a/drivers/char/tty_buffer.c ++++ b/drivers/char/tty_buffer.c +@@ -412,7 +412,8 @@ static void flush_to_ldisc(struct work_s + spin_lock_irqsave(&tty->buf.lock, flags); + + if (!test_and_set_bit(TTY_FLUSHING, &tty->flags)) { +- struct tty_buffer *head; ++ struct tty_buffer *head, *tail = tty->buf.tail; ++ int seen_tail = 0; + while ((head = tty->buf.head) != NULL) { + int count; + char *char_buf; +@@ -422,6 +423,15 @@ static void flush_to_ldisc(struct work_s + if (!count) { + if (head->next == NULL) + break; ++ /* ++ There's a possibility tty might get new buffer ++ added during the unlock window below. We could ++ end up spinning in here forever hogging the CPU ++ completely. To avoid this let's have a rest each ++ time we processed the tail buffer. ++ */ ++ if (tail == head) ++ seen_tail = 1; + tty->buf.head = head->next; + tty_buffer_free(tty, head); + continue; +@@ -431,7 +441,7 @@ static void flush_to_ldisc(struct work_s + line discipline as we want to empty the queue */ + if (test_bit(TTY_FLUSHPENDING, &tty->flags)) + break; +- if (!tty->receive_room) { ++ if (!tty->receive_room || seen_tail) { + schedule_delayed_work(&tty->buf.work, 1); + break; + } diff --git a/queue-2.6.33/tty-restore-tty_ldisc_wait_idle.patch b/queue-2.6.33/tty-restore-tty_ldisc_wait_idle.patch new file mode 100644 index 00000000000..849ef53e027 --- /dev/null +++ b/queue-2.6.33/tty-restore-tty_ldisc_wait_idle.patch @@ -0,0 +1,116 @@ +From 100eeae2c5ce23b4db93ff320ee330ef1d740151 Mon Sep 17 00:00:00 2001 +From: Jiri Slaby +Date: Sun, 31 Oct 2010 23:17:51 +0100 +Subject: TTY: restore tty_ldisc_wait_idle + +From: Jiri Slaby + +commit 100eeae2c5ce23b4db93ff320ee330ef1d740151 upstream. + +It was removed in 65b770468e98 (tty-ldisc: turn ldisc user count into +a proper refcount), but we need to wait for last user to quit the +ldisc before we close it in tty_set_ldisc. + +Otherwise weird things start to happen. There might be processes +waiting in tty_read->n_tty_read on tty->read_wait for input to appear +and at that moment, a change of ldisc is fatal. n_tty_close is called, +it frees read_buf and the waiting process is still in the middle of +reading and goes nuts after it is woken. + +Previously we prevented close to happen when others are in ldisc ops +by tty_ldisc_wait_idle in tty_set_ldisc. But the commit above removed +that. So revoke the change and test whether there is 1 user (=we), and +allow the close then. + +We can do that without ldisc/tty locks, because nobody else can open +the device due to TTY_LDISC_CHANGING bit set, so we in fact wait for +everybody to leave. + +I don't understand why tty_ldisc_lock would be needed either when the +counter is an atomic variable, so this is a lockless +tty_ldisc_wait_idle. + +On the other hand, if we fail to wait (timeout or signal), we have to +reenable the halted ldiscs, so we take ldisc lock and reuse the setup +path at the end of tty_set_ldisc. + +Signed-off-by: Jiri Slaby +Acked-by: Linus Torvalds +Tested-by: Sebastian Andrzej Siewior +LKML-Reference: <20101031104136.GA511@Chamillionaire.breakpoint.cc> +LKML-Reference: <1287669539-22644-1-git-send-email-jslaby@suse.cz> +Cc: Alan Cox +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/char/tty_ldisc.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +--- a/drivers/char/tty_ldisc.c ++++ b/drivers/char/tty_ldisc.c +@@ -47,6 +47,7 @@ + + static DEFINE_SPINLOCK(tty_ldisc_lock); + static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait); ++static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_idle); + /* Line disc dispatch table */ + static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; + +@@ -83,6 +84,7 @@ static void put_ldisc(struct tty_ldisc * + return; + } + local_irq_restore(flags); ++ wake_up(&tty_ldisc_idle); + } + + /** +@@ -530,6 +532,23 @@ static int tty_ldisc_halt(struct tty_str + } + + /** ++ * tty_ldisc_wait_idle - wait for the ldisc to become idle ++ * @tty: tty to wait for ++ * ++ * Wait for the line discipline to become idle. The discipline must ++ * have been halted for this to guarantee it remains idle. ++ */ ++static int tty_ldisc_wait_idle(struct tty_struct *tty) ++{ ++ int ret; ++ ret = wait_event_interruptible_timeout(tty_ldisc_idle, ++ atomic_read(&tty->ldisc->users) == 1, 5 * HZ); ++ if (ret < 0) ++ return ret; ++ return ret > 0 ? 0 : -EBUSY; ++} ++ ++/** + * tty_set_ldisc - set line discipline + * @tty: the terminal to set + * @ldisc: the line discipline +@@ -632,8 +651,17 @@ int tty_set_ldisc(struct tty_struct *tty + + flush_scheduled_work(); + ++ retval = tty_ldisc_wait_idle(tty); ++ + mutex_lock(&tty->ldisc_mutex); + lock_kernel(); ++ ++ /* handle wait idle failure locked */ ++ if (retval) { ++ tty_ldisc_put(new_ldisc); ++ goto enable; ++ } ++ + if (test_bit(TTY_HUPPED, &tty->flags)) { + /* We were raced by the hangup method. It will have stomped + the ldisc data and closed the ldisc down */ +@@ -667,6 +695,7 @@ int tty_set_ldisc(struct tty_struct *tty + + tty_ldisc_put(o_ldisc); + ++enable: + /* + * Allow ldisc referencing to occur again + */ diff --git a/queue-2.6.33/tty_ldisc-fix-bug-on-hangup.patch b/queue-2.6.33/tty_ldisc-fix-bug-on-hangup.patch new file mode 100644 index 00000000000..a131f1b04de --- /dev/null +++ b/queue-2.6.33/tty_ldisc-fix-bug-on-hangup.patch @@ -0,0 +1,74 @@ +From 1c95ba1e1de7edffc0c4e275e147f1a9eb1f81ae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Philippe=20R=C3=A9tornaz?= +Date: Wed, 27 Oct 2010 17:13:21 +0200 +Subject: tty_ldisc: Fix BUG() on hangup + +From: =?UTF-8?q?Philippe=20R=C3=A9tornaz?= + +commit 1c95ba1e1de7edffc0c4e275e147f1a9eb1f81ae upstream. + +A kernel BUG when bluetooth rfcomm connection drop while the associated +serial port is open is sometime triggered. + +It seems that the line discipline can disappear between the +tty_ldisc_put and tty_ldisc_get. This patch fall back to the N_TTY line +discipline if the previous discipline is not available anymore. + +Signed-off-by: Philippe Retornaz +Acked-by: Alan Cox +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/char/tty_ldisc.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +--- a/drivers/char/tty_ldisc.c ++++ b/drivers/char/tty_ldisc.c +@@ -741,9 +741,12 @@ static void tty_reset_termios(struct tty + * state closed + */ + +-static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc) ++static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc) + { +- struct tty_ldisc *ld; ++ struct tty_ldisc *ld = tty_ldisc_get(ldisc); ++ ++ if (IS_ERR(ld)) ++ return -1; + + tty_ldisc_close(tty, tty->ldisc); + tty_ldisc_put(tty->ldisc); +@@ -751,10 +754,10 @@ static void tty_ldisc_reinit(struct tty_ + /* + * Switch the line discipline back + */ +- ld = tty_ldisc_get(ldisc); +- BUG_ON(IS_ERR(ld)); + tty_ldisc_assign(tty, ld); + tty_set_termios_ldisc(tty, ldisc); ++ ++ return 0; + } + + /** +@@ -816,13 +819,16 @@ void tty_ldisc_hangup(struct tty_struct + a FIXME */ + if (tty->ldisc) { /* Not yet closed */ + if (reset == 0) { +- tty_ldisc_reinit(tty, tty->termios->c_line); +- err = tty_ldisc_open(tty, tty->ldisc); ++ ++ if (!tty_ldisc_reinit(tty, tty->termios->c_line)) ++ err = tty_ldisc_open(tty, tty->ldisc); ++ else ++ err = 1; + } + /* If the re-open fails or we reset then go to N_TTY. The + N_TTY open cannot fail */ + if (reset || err) { +- tty_ldisc_reinit(tty, N_TTY); ++ BUG_ON(tty_ldisc_reinit(tty, N_TTY)); + WARN_ON(tty_ldisc_open(tty, tty->ldisc)); + } + tty_ldisc_enable(tty);