* [PATCH] tty: Correct tty buffer flush. @ 2012-12-03 9:54 Ilya Zykov 2013-03-04 19:19 ` Ilya Zykov 0 siblings, 1 reply; 10+ messages in thread From: Ilya Zykov @ 2012-12-03 9:54 UTC (permalink / raw) To: Greg Kroah-Hartman; +Cc: Alan Cox, Ben Hutchings, linux-kernel, stable The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), when another thread can use it. It can be cause of "NULL pointer dereference". Main idea of the patch, this is never release last (struct tty_buffer) in the active buffer. Only flush data for ldisc(tty->buf.head->read = tty->buf.head->commit). At that moment driver can collect(write) data in buffer without conflict. It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. Test program and bug report you can see: https://lkml.org/lkml/2012/11/29/368 Cc: stable@vger.kernel.org Signed-off-by: Ilya Zykov <ilya@ilyx.ru> --- diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 6c9b7cd..4f02f9c 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -114,11 +114,14 @@ static void __tty_buffer_flush(struct tty_struct *tty) { struct tty_buffer *thead; - while ((thead = tty->buf.head) != NULL) { - tty->buf.head = thead->next; - tty_buffer_free(tty, thead); + if (tty->buf.head == NULL) + return; + while ((thead = tty->buf.head->next) != NULL) { + tty_buffer_free(tty, tty->buf.head); + tty->buf.head = thead; } - tty->buf.tail = NULL; + WARN_ON(tty->buf.head != tty->buf.tail); + tty->buf.head->read = tty->buf.head->commit; } /** ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] tty: Correct tty buffer flush. 2012-12-03 9:54 [PATCH] tty: Correct tty buffer flush Ilya Zykov @ 2013-03-04 19:19 ` Ilya Zykov 2013-03-17 4:06 ` Ben Hutchings 0 siblings, 1 reply; 10+ messages in thread From: Ilya Zykov @ 2013-03-04 19:19 UTC (permalink / raw) To: Ben Hutchings; +Cc: linux-kernel, stable, linux-serial On 03.12.2012 13:54, Ilya Zykov wrote: > The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), > when another thread can use it. It can be cause of "NULL pointer dereference". > Main idea of the patch, this is never release last (struct tty_buffer) in the active buffer. > Only flush data for ldisc(tty->buf.head->read = tty->buf.head->commit). > At that moment driver can collect(write) data in buffer without conflict. > It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. > Test program and bug report you can see: > https://lkml.org/lkml/2012/11/29/368 > > Cc: stable@vger.kernel.org > Signed-off-by: Ilya Zykov <ilya@ilyx.ru> > --- > diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c > index 6c9b7cd..4f02f9c 100644 > --- a/drivers/tty/tty_buffer.c > +++ b/drivers/tty/tty_buffer.c > @@ -114,11 +114,14 @@ static void __tty_buffer_flush(struct tty_struct *tty) > { > struct tty_buffer *thead; > > - while ((thead = tty->buf.head) != NULL) { > - tty->buf.head = thead->next; > - tty_buffer_free(tty, thead); > + if (tty->buf.head == NULL) > + return; > + while ((thead = tty->buf.head->next) != NULL) { > + tty_buffer_free(tty, tty->buf.head); > + tty->buf.head = thead; > } > - tty->buf.tail = NULL; > + WARN_ON(tty->buf.head != tty->buf.tail); > + tty->buf.head->read = tty->buf.head->commit; > } > > /** > You can include this patch, in 3.2 series , for improve stability, it would be merged in upstream 3.9-rc1. Thank you. Ilya. https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/drivers/tty/tty_buffer.c?id=64325a3be08d364a62ee8f84b2cf86934bc2544a ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] tty: Correct tty buffer flush. 2013-03-04 19:19 ` Ilya Zykov @ 2013-03-17 4:06 ` Ben Hutchings 0 siblings, 0 replies; 10+ messages in thread From: Ben Hutchings @ 2013-03-17 4:06 UTC (permalink / raw) To: Ilya Zykov; +Cc: linux-kernel, stable, linux-serial [-- Attachment #1: Type: text/plain, Size: 1899 bytes --] On Mon, 2013-03-04 at 23:19 +0400, Ilya Zykov wrote: > On 03.12.2012 13:54, Ilya Zykov wrote: > > The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), > > when another thread can use it. It can be cause of "NULL pointer dereference". > > Main idea of the patch, this is never release last (struct tty_buffer) in the active buffer. > > Only flush data for ldisc(tty->buf.head->read = tty->buf.head->commit). > > At that moment driver can collect(write) data in buffer without conflict. > > It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. > > Test program and bug report you can see: > > https://lkml.org/lkml/2012/11/29/368 > > > > Cc: stable@vger.kernel.org > > Signed-off-by: Ilya Zykov <ilya@ilyx.ru> > > --- > > diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c > > index 6c9b7cd..4f02f9c 100644 > > --- a/drivers/tty/tty_buffer.c > > +++ b/drivers/tty/tty_buffer.c > > @@ -114,11 +114,14 @@ static void __tty_buffer_flush(struct tty_struct *tty) > > { > > struct tty_buffer *thead; > > > > - while ((thead = tty->buf.head) != NULL) { > > - tty->buf.head = thead->next; > > - tty_buffer_free(tty, thead); > > + if (tty->buf.head == NULL) > > + return; > > + while ((thead = tty->buf.head->next) != NULL) { > > + tty_buffer_free(tty, tty->buf.head); > > + tty->buf.head = thead; > > } > > - tty->buf.tail = NULL; > > + WARN_ON(tty->buf.head != tty->buf.tail); > > + tty->buf.head->read = tty->buf.head->commit; > > } > > > > /** > > > > You can include this patch, in 3.2 series , for improve stability, > it would be merged in upstream 3.9-rc1. Added to the queue, thanks. Ben. -- Ben Hutchings Usenet is essentially a HUGE group of people passing notes in class. - Rachel Kadel, `A Quick Guide to Newsgroup Etiquette' [-- Attachment #2: This is a digitally signed message part --] [-- Type: application/pgp-signature, Size: 828 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH] tty: Correct tty buffer flush. @ 2012-12-04 13:10 Ilya Zykov 2012-12-04 14:12 ` Alan Cox 0 siblings, 1 reply; 10+ messages in thread From: Ilya Zykov @ 2012-12-04 13:10 UTC (permalink / raw) To: Greg Kroah-Hartman; +Cc: Alan Cox, linux-kernel, linux-serial The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), when another thread can use it. It can be cause of "NULL pointer dereference". Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. Only flush the data for ldisc(tty->buf.head->read = tty->buf.head->commit). At that moment driver can collect(write) data in buffer without conflict. It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. Also revert: commit c56a00a165712fd73081f40044b1e64407bb1875 tty: hold lock across tty buffer finding and buffer filling In order to delete the unneeded locks any more. Signed-off-by: Ilya Zykov <ilya@ilyx.ru> --- drivers/tty/tty_buffer.c | 96 +++++++++++++--------------------------------- 1 files changed, 27 insertions(+), 69 deletions(-) diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 91e326f..4f02f9c 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -114,11 +114,14 @@ static void __tty_buffer_flush(struct tty_struct *tty) { struct tty_buffer *thead; - while ((thead = tty->buf.head) != NULL) { - tty->buf.head = thead->next; - tty_buffer_free(tty, thead); + if (tty->buf.head == NULL) + return; + while ((thead = tty->buf.head->next) != NULL) { + tty_buffer_free(tty, tty->buf.head); + tty->buf.head = thead; } - tty->buf.tail = NULL; + WARN_ON(tty->buf.head != tty->buf.tail); + tty->buf.head->read = tty->buf.head->commit; } /** @@ -185,19 +188,25 @@ static struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size) /* Should possibly check if this fails for the largest buffer we have queued and recycle that ? */ } + /** - * __tty_buffer_request_room - grow tty buffer if needed + * tty_buffer_request_room - grow tty buffer if needed * @tty: tty structure * @size: size desired * * Make at least size bytes of linear space available for the tty * buffer. If we fail return the size we managed to find. - * Locking: Caller must hold tty->buf.lock + * + * Locking: Takes tty->buf.lock */ -static int __tty_buffer_request_room(struct tty_struct *tty, size_t size) +int tty_buffer_request_room(struct tty_struct *tty, size_t size) { struct tty_buffer *b, *n; int left; + unsigned long flags; + + spin_lock_irqsave(&tty->buf.lock, flags); + /* OPTIMISATION: We could keep a per tty "zero" sized buffer to remove this conditional if its worth it. This would be invisible to the callers */ @@ -219,29 +228,8 @@ static int __tty_buffer_request_room(struct tty_struct *tty, size_t size) size = left; } - return size; -} - - -/** - * tty_buffer_request_room - grow tty buffer if needed - * @tty: tty structure - * @size: size desired - * - * Make at least size bytes of linear space available for the tty - * buffer. If we fail return the size we managed to find. - * - * Locking: Takes tty->buf.lock - */ -int tty_buffer_request_room(struct tty_struct *tty, size_t size) -{ - unsigned long flags; - int length; - - spin_lock_irqsave(&tty->buf.lock, flags); - length = __tty_buffer_request_room(tty, size); spin_unlock_irqrestore(&tty->buf.lock, flags); - return length; + return size; } EXPORT_SYMBOL_GPL(tty_buffer_request_room); @@ -264,22 +252,14 @@ int tty_insert_flip_string_fixed_flag(struct tty_struct *tty, int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&tty->buf.lock, flags); - space = __tty_buffer_request_room(tty, goal); - tb = tty->buf.tail; + int space = tty_buffer_request_room(tty, goal); + struct tty_buffer *tb = tty->buf.tail; /* If there is no space then tb may be NULL */ - if (unlikely(space == 0)) { - spin_unlock_irqrestore(&tty->buf.lock, flags); + if (unlikely(space == 0)) break; - } memcpy(tb->char_buf_ptr + tb->used, chars, space); memset(tb->flag_buf_ptr + tb->used, flag, space); tb->used += space; - spin_unlock_irqrestore(&tty->buf.lock, flags); copied += space; chars += space; /* There is a small chance that we need to split the data over @@ -309,22 +289,14 @@ int tty_insert_flip_string_flags(struct tty_struct *tty, int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long __flags; - struct tty_buffer *tb; - ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] tty: Correct tty buffer flush. 2012-12-04 13:10 Ilya Zykov @ 2012-12-04 14:12 ` Alan Cox 2013-01-16 8:55 ` Ilya Zykov 0 siblings, 1 reply; 10+ messages in thread From: Alan Cox @ 2012-12-04 14:12 UTC (permalink / raw) To: Ilya Zykov; +Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, linux-serial On Tue, 04 Dec 2012 17:10:57 +0400 Ilya Zykov <ilya@ilyx.ru> wrote: > The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), > when another thread can use it. It can be cause of "NULL pointer dereference". > Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. > Only flush the data for ldisc(tty->buf.head->read = tty->buf.head->commit). > At that moment driver can collect(write) data in buffer without conflict. > It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. > > Also revert: > commit c56a00a165712fd73081f40044b1e64407bb1875 > tty: hold lock across tty buffer finding and buffer filling > In order to delete the unneeded locks any more. > > Signed-off-by: Ilya Zykov <ilya@ilyx.ru> Acked-by: Alan Cox <alan@linux.intel.com> ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH] tty: Correct tty buffer flush. 2012-12-04 14:12 ` Alan Cox @ 2013-01-16 8:55 ` Ilya Zykov 2013-01-19 0:07 ` Greg Kroah-Hartman 0 siblings, 1 reply; 10+ messages in thread From: Ilya Zykov @ 2013-01-16 8:55 UTC (permalink / raw) To: Alan Cox; +Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, linux-serial The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), when another thread can use it. It can be cause of "NULL pointer dereference". Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. Only flush the data for ldisc(buf->head->read = buf->head->commit). At that moment driver can collect(write) data in buffer without conflict. It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. Also revert: commit c56a00a165712fd73081f40044b1e64407bb1875 tty: hold lock across tty buffer finding and buffer filling In order to delete the unneeded locks any more. Signed-off-by: Ilya Zykov <ilya@ilyx.ru> --- drivers/tty/tty_buffer.c | 105 +++++++++++++--------------------------------- 1 files changed, 29 insertions(+), 76 deletions(-) diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 45d9161..8a3333d 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -119,11 +119,14 @@ static void __tty_buffer_flush(struct tty_port *port) struct tty_bufhead *buf = &port->buf; struct tty_buffer *thead; - while ((thead = buf->head) != NULL) { - buf->head = thead->next; - tty_buffer_free(port, thead); + if (unlikely(buf->head == NULL)) + return; + while ((thead = buf->head->next) != NULL) { + tty_buffer_free(port, buf->head); + buf->head = thead; } - buf->tail = NULL; + WARN_ON(buf->head != buf->tail); + buf->head->read = buf->head->commit; } /** @@ -193,20 +196,26 @@ static struct tty_buffer *tty_buffer_find(struct tty_port *port, size_t size) /* Should possibly check if this fails for the largest buffer we have queued and recycle that ? */ } + /** - * __tty_buffer_request_room - grow tty buffer if needed + * tty_buffer_request_room - grow tty buffer if needed * @tty: tty structure * @size: size desired * * Make at least size bytes of linear space available for the tty * buffer. If we fail return the size we managed to find. - * Locking: Caller must hold port->buf.lock + * + * Locking: Takes tty->port->buf.lock */ -static int __tty_buffer_request_room(struct tty_port *port, size_t size) +int tty_buffer_request_room(struct tty_struct *tty, size_t size) { - struct tty_bufhead *buf = &port->buf; + struct tty_bufhead *buf = &tty->port->buf; struct tty_buffer *b, *n; int left; + unsigned long flags; + + spin_lock_irqsave(&buf->lock, flags); + /* OPTIMISATION: We could keep a per tty "zero" sized buffer to remove this conditional if its worth it. This would be invisible to the callers */ @@ -218,7 +227,7 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size) if (left < size) { /* This is the slow path - looking for new buffers to use */ - if ((n = tty_buffer_find(port, size)) != NULL) { + if ((n = tty_buffer_find(tty->port, size)) != NULL) { if (b != NULL) { b->next = n; b->commit = b->used; @@ -229,31 +238,9 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size) size = left; } + spin_unlock_irqrestore(&buf->lock, flags); return size; } - - -/** - * tty_buffer_request_room - grow tty buffer if needed - * @tty: tty structure - * @size: size desired - * - * Make at least size bytes of linear space available for the tty - * buffer. If we fail return the size we managed to find. - * - * Locking: Takes port->buf.lock - */ -int tty_buffer_request_room(struct tty_struct *tty, size_t size) -{ - struct tty_port *port = tty->port; - unsigned long flags; - int length; - - spin_lock_irqsave(&port->buf.lock, flags); - length = __tty_buffer_request_room(port, size); - spin_unlock_irqrestore(&port->buf.lock, flags); - return length; -} EXPORT_SYMBOL_GPL(tty_buffer_request_room); /** @@ -272,26 +259,17 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); int tty_insert_flip_string_fixed_flag(struct tty_struct *tty, const unsigned char *chars, char flag, size_t size) { - struct tty_bufhead *buf = &tty->port->buf; int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, flags); - space = __tty_buffer_request_room(tty->port, goal); - tb = buf->tail; + int space = tty_buffer_request_room(tty, goal); + struct tty_buffer *tb = tty->port->buf.tail; /* If there is no space then tb may be NULL */ - if (unlikely(space == 0)) { - spin_unlock_irqrestore(&buf->lock, flags); + if (unlikely(space == 0)) break; - } memcpy(tb->char_buf_ptr + tb->used, chars, space); memset(tb->flag_buf_ptr + tb->used, flag, space); tb->used += space; - spin_unlock_irqrestore(&buf->lock, flags); copied += space; chars += space; /* There is a small chance that we need to split the data over @@ -318,26 +296,17 @@ EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag); int tty_insert_flip_string_flags(struct tty_struct *tty, const unsigned char *chars, const char *flags, size_t size) { - struct tty_bufhead *buf = &tty->port->buf; int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long __flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, __flags); - space = __tty_buffer_request_room(tty->port, goal); - tb = buf->tail; + int space = tty_buffer_request_room(tty, goal); + struct tty_buffer *tb = tty->port->buf.tail; /* If there is no space then tb may be NULL */ - if (unlikely(space == 0)) { - spin_unlock_irqrestore(&buf->lock, __flags); + if (unlikely(space == 0)) break; - } memcpy(tb->char_buf_ptr + tb->used, chars, space); memcpy(tb->flag_buf_ptr + tb->used, flags, space); tb->used += space; - spin_unlock_irqrestore(&buf->lock, __flags); copied += space; chars += space; flags += space; @@ -393,21 +362,13 @@ EXPORT_SYMBOL(tty_schedule_flip); int tty_prepare_flip_string(struct tty_struct *tty, unsigned char **chars, size_t size) { - struct tty_bufhead *buf = &tty->port->buf; - int space; - unsigned long flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, flags); - space = __tty_buffer_request_room(tty->port, size); - - tb = buf->tail; + int space = tty_buffer_request_room(tty, size); if (likely(space)) { + struct tty_buffer *tb = tty->port->buf.tail; *chars = tb->char_buf_ptr + tb->used; memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); tb->used += space; } - spin_unlock_irqrestore(&buf->lock, flags); return space; } EXPORT_SYMBOL_GPL(tty_prepare_flip_string); @@ -431,21 +392,13 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string); int tty_prepare_flip_string_flags(struct tty_struct *tty, unsigned char **chars, char **flags, size_t size) { - struct tty_bufhead *buf = &tty->port->buf; - int space; - unsigned long __flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, __flags); - space = __tty_buffer_request_room(tty->port, size); - - tb = buf->tail; + int space = tty_buffer_request_room(tty, size); if (likely(space)) { + struct tty_buffer *tb = tty->port->buf.tail; *chars = tb->char_buf_ptr + tb->used; *flags = tb->flag_buf_ptr + tb->used; tb->used += space; } - spin_unlock_irqrestore(&buf->lock, __flags); return space; } EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags); ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] tty: Correct tty buffer flush. 2013-01-16 8:55 ` Ilya Zykov @ 2013-01-19 0:07 ` Greg Kroah-Hartman 2013-01-19 14:16 ` Ilya Zykov 0 siblings, 1 reply; 10+ messages in thread From: Greg Kroah-Hartman @ 2013-01-19 0:07 UTC (permalink / raw) To: Ilya Zykov; +Cc: Alan Cox, Alan Cox, linux-kernel, linux-serial On Wed, Jan 16, 2013 at 12:55:00PM +0400, Ilya Zykov wrote: > The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), > when another thread can use it. It can be cause of "NULL pointer dereference". > Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. > Only flush the data for ldisc(buf->head->read = buf->head->commit). > At that moment driver can collect(write) data in buffer without conflict. > It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. > > Also revert: > commit c56a00a165712fd73081f40044b1e64407bb1875 > tty: hold lock across tty buffer finding and buffer filling > In order to delete the unneeded locks any more. This patch doesn't apply to my tty-next branch, can you redo it against linux-next so that I can apply it? thanks, greg k-h ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH] tty: Correct tty buffer flush. 2013-01-19 0:07 ` Greg Kroah-Hartman @ 2013-01-19 14:16 ` Ilya Zykov [not found] ` <CA+icZUXG-srV6i9PaMgdhRG2gCpU4T+VaxhZj_=E4XjAgh8REQ@mail.gmail.com> 0 siblings, 1 reply; 10+ messages in thread From: Ilya Zykov @ 2013-01-19 14:16 UTC (permalink / raw) To: Greg Kroah-Hartman Cc: Alan Cox, Alan Cox, linux-kernel, linux-serial, Sedat Dilek The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), when another thread can use it. It can be cause of "NULL pointer dereference". Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. Only flush the data for ldisc(buf->head->read = buf->head->commit). At that moment driver can collect(write) data in buffer without conflict. It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. Also revert: commit c56a00a165712fd73081f40044b1e64407bb1875 tty: hold lock across tty buffer finding and buffer filling In order to delete the unneeded locks any more. Signed-off-by: Ilya Zykov <ilya@ilyx.ru> --- drivers/tty/tty_buffer.c | 92 +++++++++++----------------------------------- 1 files changed, 22 insertions(+), 70 deletions(-) diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index d6969f6..61ec4dd 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -119,11 +119,14 @@ static void __tty_buffer_flush(struct tty_port *port) struct tty_bufhead *buf = &port->buf; struct tty_buffer *thead; - while ((thead = buf->head) != NULL) { - buf->head = thead->next; - tty_buffer_free(port, thead); + if (unlikely(buf->head == NULL)) + return; + while ((thead = buf->head->next) != NULL) { + tty_buffer_free(port, buf->head); + buf->head = thead; } - buf->tail = NULL; + WARN_ON(buf->head != buf->tail); + buf->head->read = buf->head->commit; } /** @@ -194,19 +197,22 @@ static struct tty_buffer *tty_buffer_find(struct tty_port *port, size_t size) have queued and recycle that ? */ } /** - * __tty_buffer_request_room - grow tty buffer if needed + * tty_buffer_request_room - grow tty buffer if needed * @tty: tty structure * @size: size desired * * Make at least size bytes of linear space available for the tty * buffer. If we fail return the size we managed to find. - * Locking: Caller must hold port->buf.lock + * + * Locking: Takes port->buf.lock */ -static int __tty_buffer_request_room(struct tty_port *port, size_t size) +int tty_buffer_request_room(struct tty_port *port, size_t size) { struct tty_bufhead *buf = &port->buf; struct tty_buffer *b, *n; int left; + unsigned long flags; + spin_lock_irqsave(&buf->lock, flags); /* OPTIMISATION: We could keep a per tty "zero" sized buffer to remove this conditional if its worth it. This would be invisible to the callers */ @@ -228,31 +234,9 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size) } else size = left; } - + spin_unlock_irqrestore(&buf->lock, flags); return size; } - - -/** - * tty_buffer_request_room - grow tty buffer if needed - * @port: tty port structure - * @size: size desired - * - * Make at least size bytes of linear space available for the tty - * buffer. If we fail return the size we managed to find. - * - * Locking: Takes port->buf.lock - */ -int tty_buffer_request_room(struct tty_port *port, size_t size) -{ - unsigned long flags; - int length; - - spin_lock_irqsave(&port->buf.lock, flags); - length = __tty_buffer_request_room(port, size); - spin_unlock_irqrestore(&port->buf.lock, flags); - return length; -} EXPORT_SYMBOL_GPL(tty_buffer_request_room); /** @@ -271,26 +255,18 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); int tty_insert_flip_string_fixed_flag(struct tty_port *port, const unsigned char *chars, char flag, size_t size) { - struct tty_bufhead *buf = &port->buf; int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, flags); - space = __tty_buffer_request_room(port, goal); - tb = buf->tail; + int space = tty_buffer_request_room(port, goal); + struct tty_buffer *tb = port->buf.tail; /* If there is no space then tb may be NULL */ if (unlikely(space == 0)) { - spin_unlock_irqrestore(&buf->lock, flags); break; } memcpy(tb->char_buf_ptr + tb->used, chars, space); memset(tb->flag_buf_ptr + tb->used, flag, space); tb->used += space; - spin_unlock_irqrestore(&buf->lock, flags); copied += space; chars += space; /* There is a small chance that we need to split the data over @@ -317,26 +293,18 @@ EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag); int tty_insert_flip_string_flags(struct tty_port *port, const unsigned char *chars, const char *flags, size_t size) { - struct tty_bufhead *buf = &port->buf; int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); - int space; - unsigned long __flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, __flags); - space = __tty_buffer_request_room(port, goal); - tb = buf->tail; + int space = tty_buffer_request_room(port, goal); + struct tty_buffer *tb = port->buf.tail; /* If there is no space then tb may be NULL */ if (unlikely(space == 0)) { - spin_unlock_irqrestore(&buf->lock, __flags); break; } memcpy(tb->char_buf_ptr + tb->used, chars, space); memcpy(tb->flag_buf_ptr + tb->used, flags, space); tb->used += space; - spin_unlock_irqrestore(&buf->lock, __flags); copied += space; chars += space; flags += space; @@ -392,21 +360,13 @@ EXPORT_SYMBOL(tty_schedule_flip); int tty_prepare_flip_string(struct tty_port *port, unsigned char **chars, size_t size) { - struct tty_bufhead *buf = &port->buf; - int space; - unsigned long flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, flags); - space = __tty_buffer_request_room(port, size); - - tb = buf->tail; + int space = tty_buffer_request_room(port, size); if (likely(space)) { + struct tty_buffer *tb = port->buf.tail; *chars = tb->char_buf_ptr + tb->used; memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); tb->used += space; } - spin_unlock_irqrestore(&buf->lock, flags); return space; } EXPORT_SYMBOL_GPL(tty_prepare_flip_string); @@ -430,21 +390,13 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string); int tty_prepare_flip_string_flags(struct tty_port *port, unsigned char **chars, char **flags, size_t size) { - struct tty_bufhead *buf = &port->buf; - int space; - unsigned long __flags; - struct tty_buffer *tb; - - spin_lock_irqsave(&buf->lock, __flags); - space = __tty_buffer_request_room(port, size); - - tb = buf->tail; + int space = tty_buffer_request_room(port, size); if (likely(space)) { + struct tty_buffer *tb = port->buf.tail; *chars = tb->char_buf_ptr + tb->used; *flags = tb->flag_buf_ptr + tb->used; tb->used += space; } - spin_unlock_irqrestore(&buf->lock, __flags); return space; } EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags); ^ permalink raw reply related [flat|nested] 10+ messages in thread
[parent not found: <CA+icZUXG-srV6i9PaMgdhRG2gCpU4T+VaxhZj_=E4XjAgh8REQ@mail.gmail.com>]
* Re: [PATCH] tty: Correct tty buffer flush. [not found] ` <CA+icZUXG-srV6i9PaMgdhRG2gCpU4T+VaxhZj_=E4XjAgh8REQ@mail.gmail.com> @ 2013-01-19 15:47 ` Sedat Dilek 0 siblings, 0 replies; 10+ messages in thread From: Sedat Dilek @ 2013-01-19 15:47 UTC (permalink / raw) To: Ilya Zykov Cc: Greg Kroah-Hartman, Alan Cox, Alan Cox, linux-kernel, linux-serial On Sat, Jan 19, 2013 at 4:12 PM, Sedat Dilek <sedat.dilek@gmail.com> wrote: > On Sat, Jan 19, 2013 at 3:16 PM, Ilya Zykov <ilya@ilyx.ru> wrote: >> The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()), >> when another thread can use it. It can be cause of "NULL pointer dereference". >> Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer. >> Only flush the data for ldisc(buf->head->read = buf->head->commit). >> At that moment driver can collect(write) data in buffer without conflict. >> It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc. >> >> Also revert: >> commit c56a00a165712fd73081f40044b1e64407bb1875 >> tty: hold lock across tty buffer finding and buffer filling >> In order to delete the unneeded locks any more. >> >> Signed-off-by: Ilya Zykov <ilya@ilyx.ru> >> > > I have tested your patch on top of Linux-Next (next-20130118) plus my > own patchset (see file attachments)! > > [ TESTCASE ] > > # grep CONFIG_PM_DEBUG /boot/config-$(uname -r) > CONFIG_PM_DEBUG=y > > # echo "freezer" > /sys/power/pm_test > > # cat /sys/power/pm_test > none core processors platform devices [freezer] > > # echo mem > /sys/power/state && sleep 1 <--- 1st S/R: OK > > # echo mem > /sys/power/state && sleep 1 <--- 2nd S/R: OK, but caused > a call-trace (see above)! > > [ /TESTCASE ] > > Unfortunately, I get now a different call-trace on the 2nd freezer/pm-test... > > +[ 368.891613] PM: Preparing system for mem sleep > +[ 373.886722] Freezing user space processes ... (elapsed 0.01 seconds) done. > +[ 373.902741] Freezing remaining freezable tasks ... > +[ 393.904587] Freezing of tasks failed after 20.01 seconds (1 tasks > refusing to freeze, wq_busy=0): > +[ 393.904714] jbd2/loop0-8 D 0000000000000003 0 304 2 0x00000000 > +[ 393.904723] ffff880117525b68 0000000000000046 00000000ffffffff > ffff8801195d9400 > +[ 393.904731] ffff880117d74560 ffff880117525fd8 ffff880117525fd8 > ffff880117525fd8 > +[ 393.904738] ffff88004212c560 ffff880117d74560 ffff880117525b68 > ffff88011fad4738 > +[ 393.904745] Call Trace: > +[ 393.904764] [<ffffffff811c6830>] ? __wait_on_buffer+0x30/0x30 > +[ 393.904772] [<ffffffff816b5079>] schedule+0x29/0x70 > +[ 393.904778] [<ffffffff816b514f>] io_schedule+0x8f/0xd0 > +[ 393.904785] [<ffffffff811c683e>] sleep_on_buffer+0xe/0x20 > +[ 393.904794] [<ffffffff816b394f>] __wait_on_bit+0x5f/0x90 > +[ 393.904801] [<ffffffff811c6830>] ? __wait_on_buffer+0x30/0x30 > +[ 393.904809] [<ffffffff816b39fc>] out_of_line_wait_on_bit+0x7c/0x90 > +[ 393.904817] [<ffffffff8107eb00>] ? autoremove_wake_function+0x40/0x40 > +[ 393.904824] [<ffffffff811c682e>] __wait_on_buffer+0x2e/0x30 > +[ 393.904836] [<ffffffff8128a14f>] > jbd2_journal_commit_transaction+0xdef/0x1960 > +[ 393.904844] [<ffffffff816b620e>] ? _raw_spin_lock_irqsave+0x2e/0x40 > +[ 393.904853] [<ffffffff81069fbf>] ? try_to_del_timer_sync+0x4f/0x70 > +[ 393.904859] [<ffffffff8128e938>] kjournald2+0xb8/0x240 > +[ 393.904865] [<ffffffff8107eac0>] ? add_wait_queue+0x60/0x60 > +[ 393.904871] [<ffffffff8128e880>] ? commit_timeout+0x10/0x10 > +[ 393.904877] [<ffffffff8107ded0>] kthread+0xc0/0xd0 > +[ 393.904883] [<ffffffff8107de10>] ? flush_kthread_worker+0xb0/0xb0 > +[ 393.904889] [<ffffffff816bea2c>] ret_from_fork+0x7c/0xb0 > +[ 393.904894] [<ffffffff8107de10>] ? flush_kthread_worker+0xb0/0xb0 > +[ 393.904972] > +[ 393.904975] Restarting kernel threads ... done. > +[ 393.905163] Restarting tasks ... done. > +[ 393.914283] video LNXVIDEO:00: Restoring backlight state > > If this ring one or more bells to you let me know! > > Please, have a look at the file attachments! > Thanks. > > Hope this helps us to track the problem! > I turned on... CONFIG_EXT4_DEBUG=y CONFIG_JBD2_DEBUG=y ...to see more light in the dark. - Sedat - > - Sedat - > >> --- >> drivers/tty/tty_buffer.c | 92 +++++++++++----------------------------------- >> 1 files changed, 22 insertions(+), 70 deletions(-) >> >> diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c >> index d6969f6..61ec4dd 100644 >> --- a/drivers/tty/tty_buffer.c >> +++ b/drivers/tty/tty_buffer.c >> @@ -119,11 +119,14 @@ static void __tty_buffer_flush(struct tty_port *port) >> struct tty_bufhead *buf = &port->buf; >> struct tty_buffer *thead; >> >> - while ((thead = buf->head) != NULL) { >> - buf->head = thead->next; >> - tty_buffer_free(port, thead); >> + if (unlikely(buf->head == NULL)) >> + return; >> + while ((thead = buf->head->next) != NULL) { >> + tty_buffer_free(port, buf->head); >> + buf->head = thead; >> } >> - buf->tail = NULL; >> + WARN_ON(buf->head != buf->tail); >> + buf->head->read = buf->head->commit; >> } >> >> /** >> @@ -194,19 +197,22 @@ static struct tty_buffer *tty_buffer_find(struct tty_port *port, size_t size) >> have queued and recycle that ? */ >> } >> /** >> - * __tty_buffer_request_room - grow tty buffer if needed >> + * tty_buffer_request_room - grow tty buffer if needed >> * @tty: tty structure >> * @size: size desired >> * >> * Make at least size bytes of linear space available for the tty >> * buffer. If we fail return the size we managed to find. >> - * Locking: Caller must hold port->buf.lock >> + * >> + * Locking: Takes port->buf.lock >> */ >> -static int __tty_buffer_request_room(struct tty_port *port, size_t size) >> +int tty_buffer_request_room(struct tty_port *port, size_t size) >> { >> struct tty_bufhead *buf = &port->buf; >> struct tty_buffer *b, *n; >> int left; >> + unsigned long flags; >> + spin_lock_irqsave(&buf->lock, flags); >> /* OPTIMISATION: We could keep a per tty "zero" sized buffer to >> remove this conditional if its worth it. This would be invisible >> to the callers */ >> @@ -228,31 +234,9 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size) >> } else >> size = left; >> } >> - >> + spin_unlock_irqrestore(&buf->lock, flags); >> return size; >> } >> - >> - >> -/** >> - * tty_buffer_request_room - grow tty buffer if needed >> - * @port: tty port structure >> - * @size: size desired >> - * >> - * Make at least size bytes of linear space available for the tty >> - * buffer. If we fail return the size we managed to find. >> - * >> - * Locking: Takes port->buf.lock >> - */ >> -int tty_buffer_request_room(struct tty_port *port, size_t size) >> -{ >> - unsigned long flags; >> - int length; >> - >> - spin_lock_irqsave(&port->buf.lock, flags); >> - length = __tty_buffer_request_room(port, size); >> - spin_unlock_irqrestore(&port->buf.lock, flags); >> - return length; >> -} >> EXPORT_SYMBOL_GPL(tty_buffer_request_room); >> >> /** >> @@ -271,26 +255,18 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); >> int tty_insert_flip_string_fixed_flag(struct tty_port *port, >> const unsigned char *chars, char flag, size_t size) >> { >> - struct tty_bufhead *buf = &port->buf; >> int copied = 0; >> do { >> int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); >> - int space; >> - unsigned long flags; >> - struct tty_buffer *tb; >> - >> - spin_lock_irqsave(&buf->lock, flags); >> - space = __tty_buffer_request_room(port, goal); >> - tb = buf->tail; >> + int space = tty_buffer_request_room(port, goal); >> + struct tty_buffer *tb = port->buf.tail; >> /* If there is no space then tb may be NULL */ >> if (unlikely(space == 0)) { >> - spin_unlock_irqrestore(&buf->lock, flags); >> break; >> } >> memcpy(tb->char_buf_ptr + tb->used, chars, space); >> memset(tb->flag_buf_ptr + tb->used, flag, space); >> tb->used += space; >> - spin_unlock_irqrestore(&buf->lock, flags); >> copied += space; >> chars += space; >> /* There is a small chance that we need to split the data over >> @@ -317,26 +293,18 @@ EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag); >> int tty_insert_flip_string_flags(struct tty_port *port, >> const unsigned char *chars, const char *flags, size_t size) >> { >> - struct tty_bufhead *buf = &port->buf; >> int copied = 0; >> do { >> int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); >> - int space; >> - unsigned long __flags; >> - struct tty_buffer *tb; >> - >> - spin_lock_irqsave(&buf->lock, __flags); >> - space = __tty_buffer_request_room(port, goal); >> - tb = buf->tail; >> + int space = tty_buffer_request_room(port, goal); >> + struct tty_buffer *tb = port->buf.tail; >> /* If there is no space then tb may be NULL */ >> if (unlikely(space == 0)) { >> - spin_unlock_irqrestore(&buf->lock, __flags); >> break; >> } >> memcpy(tb->char_buf_ptr + tb->used, chars, space); >> memcpy(tb->flag_buf_ptr + tb->used, flags, space); >> tb->used += space; >> - spin_unlock_irqrestore(&buf->lock, __flags); >> copied += space; >> chars += space; >> flags += space; >> @@ -392,21 +360,13 @@ EXPORT_SYMBOL(tty_schedule_flip); >> int tty_prepare_flip_string(struct tty_port *port, unsigned char **chars, >> size_t size) >> { >> - struct tty_bufhead *buf = &port->buf; >> - int space; >> - unsigned long flags; >> - struct tty_buffer *tb; >> - >> - spin_lock_irqsave(&buf->lock, flags); >> - space = __tty_buffer_request_room(port, size); >> - >> - tb = buf->tail; >> + int space = tty_buffer_request_room(port, size); >> if (likely(space)) { >> + struct tty_buffer *tb = port->buf.tail; >> *chars = tb->char_buf_ptr + tb->used; >> memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); >> tb->used += space; >> } >> - spin_unlock_irqrestore(&buf->lock, flags); >> return space; >> } >> EXPORT_SYMBOL_GPL(tty_prepare_flip_string); >> @@ -430,21 +390,13 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string); >> int tty_prepare_flip_string_flags(struct tty_port *port, >> unsigned char **chars, char **flags, size_t size) >> { >> - struct tty_bufhead *buf = &port->buf; >> - int space; >> - unsigned long __flags; >> - struct tty_buffer *tb; >> - >> - spin_lock_irqsave(&buf->lock, __flags); >> - space = __tty_buffer_request_room(port, size); >> - >> - tb = buf->tail; >> + int space = tty_buffer_request_room(port, size); >> if (likely(space)) { >> + struct tty_buffer *tb = port->buf.tail; >> *chars = tb->char_buf_ptr + tb->used; >> *flags = tb->flag_buf_ptr + tb->used; >> tb->used += space; >> } >> - spin_unlock_irqrestore(&buf->lock, __flags); >> return space; >> } >> EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags); ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] tty: Correct tty buffer flush. @ 2013-01-19 13:39 Sedat Dilek 0 siblings, 0 replies; 10+ messages in thread From: Sedat Dilek @ 2013-01-19 13:39 UTC (permalink / raw) To: Ilya Zykov; +Cc: LKML, linux-next, Greg Kroah-Hartman, Jiri Slaby, Alan Cox Hi Ilya, can you please CC me on this patch against tty-next? Looks like I am hitting it in Linux-Next (next-20130118). For more details have look at [1] and [2]. Thanks in advance! Regards, - Sedat - [1] http://marc.info/?l=linux-kernel&m=135860252214223&w=2 [2] http://marc.info/?t=135854714000003&r=1&w=2 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2013-03-17 4:06 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-12-03 9:54 [PATCH] tty: Correct tty buffer flush Ilya Zykov
2013-03-04 19:19 ` Ilya Zykov
2013-03-17 4:06 ` Ben Hutchings
-- strict thread matches above, loose matches on Subject: below --
2012-12-04 13:10 Ilya Zykov
2012-12-04 14:12 ` Alan Cox
2013-01-16 8:55 ` Ilya Zykov
2013-01-19 0:07 ` Greg Kroah-Hartman
2013-01-19 14:16 ` Ilya Zykov
[not found] ` <CA+icZUXG-srV6i9PaMgdhRG2gCpU4T+VaxhZj_=E4XjAgh8REQ@mail.gmail.com>
2013-01-19 15:47 ` Sedat Dilek
2013-01-19 13:39 Sedat Dilek
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox