From 7098296a362a96051fa120abf48f0095818b99cd Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 17 Sep 2015 17:17:08 +0200 Subject: tty: fix data race in flush_to_ldisc flush_to_ldisc reads port->itty and checks that it is not NULL, concurrently release_tty sets port->itty to NULL. It is possible that flush_to_ldisc loads port->itty once, ensures that it is not NULL, but then reloads it again and uses. The second load can already return NULL, which will cause a crash. Use READ_ONCE to read port->itty. The data race was found with KernelThreadSanitizer (KTSAN). Signed-off-by: Dmitry Vyukov Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/tty/tty_buffer.c') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 5a3fa8913880..23de97de09a4 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -467,7 +467,7 @@ static void flush_to_ldisc(struct work_struct *work) struct tty_struct *tty; struct tty_ldisc *disc; - tty = port->itty; + tty = READ_ONCE(port->itty); if (tty == NULL) return; -- cgit v1.2.3 From 9e6b7cd7e77d4ca43b57c726d9bfa86d06e0567f Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 17 Sep 2015 17:17:09 +0200 Subject: tty: fix data race in tty_buffer_flush tty_buffer_flush frees not acquired buffers. As the result, for example, read of b->size in tty_buffer_free can return garbage value which will lead to a huge buffer hanging in the freelist. This is just the benignest manifestation of freeing of a not acquired object. If the object is passed to kfree, heap can be corrupted. Acquire visibility over the buffer before freeing it. The data race was found with KernelThreadSanitizer (KTSAN). Signed-off-by: Dmitry Vyukov Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/tty/tty_buffer.c') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 23de97de09a4..b3a5e33948c1 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -242,7 +242,10 @@ void tty_buffer_flush(struct tty_struct *tty, struct tty_ldisc *ld) atomic_inc(&buf->priority); mutex_lock(&buf->lock); - while ((next = buf->head->next) != NULL) { + /* paired w/ release in __tty_buffer_request_room; ensures there are + * no pending memory accesses to the freed buffer + */ + while ((next = smp_load_acquire(&buf->head->next)) != NULL) { tty_buffer_free(port, buf->head); buf->head = next; } -- cgit v1.2.3 From facd885c75067c2a88b72622dfb0fa4b9510da5e Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 17 Sep 2015 17:17:10 +0200 Subject: tty: fix data race on tty_buffer.commit Race on buffer data happens when newly committed data is picked up by an old flush work in the following scenario: __tty_buffer_request_room does a plain write of tail->commit, no barriers were executed before that. At this point flush_to_ldisc reads this new value of commit, and reads buffer data, no barriers in between. The committed buffer data is not necessary visible to flush_to_ldisc. Similar bug happens when tty_schedule_flip commits data. Update commit with smp_store_release and read commit with smp_load_acquire, as it is commit that signals data readiness. This is orthogonal to the existing synchronization on tty_buffer.next, which is required to not dismiss a buffer with unconsumed data. The data race was found with KernelThreadSanitizer (KTSAN). Signed-off-by: Dmitry Vyukov Reviewed-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'drivers/tty/tty_buffer.c') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index b3a5e33948c1..a660ab181cca 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -293,7 +293,10 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size, if (n != NULL) { n->flags = flags; buf->tail = n; - b->commit = b->used; + /* paired w/ acquire in flush_to_ldisc(); ensures + * flush_to_ldisc() sees buffer data. + */ + smp_store_release(&b->commit, b->used); /* paired w/ acquire in flush_to_ldisc(); ensures the * latest commit value can be read before the head is * advanced to the next buffer @@ -396,7 +399,10 @@ void tty_schedule_flip(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; - buf->tail->commit = buf->tail->used; + /* paired w/ acquire in flush_to_ldisc(); ensures + * flush_to_ldisc() sees buffer data. + */ + smp_store_release(&buf->tail->commit, buf->tail->used); schedule_work(&buf->work); } EXPORT_SYMBOL(tty_schedule_flip); @@ -494,7 +500,10 @@ static void flush_to_ldisc(struct work_struct *work) * is advancing to the next buffer */ next = smp_load_acquire(&head->next); - count = head->commit - head->read; + /* paired w/ release in __tty_buffer_request_room() or in + * tty_buffer_flush(); ensures we see the committed buffer data + */ + count = smp_load_acquire(&head->commit) - head->read; if (!count) { if (next == NULL) { check_other_closed(tty); -- cgit v1.2.3