diff options
author | Alan Cox <alan@lxorguk.ukuu.org.uk> | 2007-08-10 16:01:05 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-08-11 18:47:41 -0400 |
commit | 42fd552e8647316757ded0176466c41d17934dcf (patch) | |
tree | 07b0f4ed1934c05c6495c62ba943ab90bde6be84 | |
parent | f8a745942b1b7f052cb76bb8a893d12cb6329c84 (diff) |
fix serial buffer memory leak
Patch c5c34d4862e18ef07c1276d233507f540fb5a532 (tty: flush flip buffer on
ldisc input queue flush) introduces a race condition which can lead to memory
leaks.
The problem can be triggered when tcflush() is called when data are being
pushed to the line discipline driver by flush_to_ldisc().
flush_to_ldisc() releases tty->buf.lock when calling the line discipline
receive_buf function. At that poing tty_buffer_flush() kicks in and sets both
tty->buf.head and tty->buf.tail to NULL. When flush_to_ldisc() finishes, it
restores tty->buf.head but doesn't touch tty->buf.tail. This corrups the
buffer queue, and the next call to tty_buffer_request_room() will allocate a
new buffer and overwrite tty->buf.head. The previous buffer is then lost
forever without being released.
(Thanks to Laurent for the above text, for finding, disgnosing and reporting
the bug)
- Use tty->flags bits for the flush status.
- Wait for the flag to clear again before returning
- Fix the doc error noted
- Fix flush of empty queue leaving stale flushpending
[akpm@linux-foundation.org: cleanup]
Signed-off-by: Alan Cox <alan@redhat.com>
Acked-by: Paul Fulghum <paulkf@microgate.com>
Cc: Laurent Pinchart <laurentp@cse-semaphore.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/char/tty_io.c | 56 | ||||
-rw-r--r-- | include/linux/tty.h | 2 |
2 files changed, 52 insertions, 6 deletions
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index de37ebc3a4cf..51ea93cab6c4 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c | |||
@@ -369,25 +369,54 @@ static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b) | |||
369 | } | 369 | } |
370 | 370 | ||
371 | /** | 371 | /** |
372 | * tty_buffer_flush - flush full tty buffers | 372 | * __tty_buffer_flush - flush full tty buffers |
373 | * @tty: tty to flush | 373 | * @tty: tty to flush |
374 | * | 374 | * |
375 | * flush all the buffers containing receive data | 375 | * flush all the buffers containing receive data. Caller must |
376 | * hold the buffer lock and must have ensured no parallel flush to | ||
377 | * ldisc is running. | ||
376 | * | 378 | * |
377 | * Locking: none | 379 | * Locking: Caller must hold tty->buf.lock |
378 | */ | 380 | */ |
379 | 381 | ||
380 | static void tty_buffer_flush(struct tty_struct *tty) | 382 | static void __tty_buffer_flush(struct tty_struct *tty) |
381 | { | 383 | { |
382 | struct tty_buffer *thead; | 384 | struct tty_buffer *thead; |
383 | unsigned long flags; | ||
384 | 385 | ||
385 | spin_lock_irqsave(&tty->buf.lock, flags); | ||
386 | while((thead = tty->buf.head) != NULL) { | 386 | while((thead = tty->buf.head) != NULL) { |
387 | tty->buf.head = thead->next; | 387 | tty->buf.head = thead->next; |
388 | tty_buffer_free(tty, thead); | 388 | tty_buffer_free(tty, thead); |
389 | } | 389 | } |
390 | tty->buf.tail = NULL; | 390 | tty->buf.tail = NULL; |
391 | } | ||
392 | |||
393 | /** | ||
394 | * tty_buffer_flush - flush full tty buffers | ||
395 | * @tty: tty to flush | ||
396 | * | ||
397 | * flush all the buffers containing receive data. If the buffer is | ||
398 | * being processed by flush_to_ldisc then we defer the processing | ||
399 | * to that function | ||
400 | * | ||
401 | * Locking: none | ||
402 | */ | ||
403 | |||
404 | static void tty_buffer_flush(struct tty_struct *tty) | ||
405 | { | ||
406 | unsigned long flags; | ||
407 | spin_lock_irqsave(&tty->buf.lock, flags); | ||
408 | |||
409 | /* If the data is being pushed to the tty layer then we can't | ||
410 | process it here. Instead set a flag and the flush_to_ldisc | ||
411 | path will process the flush request before it exits */ | ||
412 | if (test_bit(TTY_FLUSHING, &tty->flags)) { | ||
413 | set_bit(TTY_FLUSHPENDING, &tty->flags); | ||
414 | spin_unlock_irqrestore(&tty->buf.lock, flags); | ||
415 | wait_event(tty->read_wait, | ||
416 | test_bit(TTY_FLUSHPENDING, &tty->flags) == 0); | ||
417 | return; | ||
418 | } else | ||
419 | __tty_buffer_flush(tty); | ||
391 | spin_unlock_irqrestore(&tty->buf.lock, flags); | 420 | spin_unlock_irqrestore(&tty->buf.lock, flags); |
392 | } | 421 | } |
393 | 422 | ||
@@ -3594,6 +3623,7 @@ static void flush_to_ldisc(struct work_struct *work) | |||
3594 | return; | 3623 | return; |
3595 | 3624 | ||
3596 | spin_lock_irqsave(&tty->buf.lock, flags); | 3625 | spin_lock_irqsave(&tty->buf.lock, flags); |
3626 | set_bit(TTY_FLUSHING, &tty->flags); /* So we know a flush is running */ | ||
3597 | head = tty->buf.head; | 3627 | head = tty->buf.head; |
3598 | if (head != NULL) { | 3628 | if (head != NULL) { |
3599 | tty->buf.head = NULL; | 3629 | tty->buf.head = NULL; |
@@ -3607,6 +3637,11 @@ static void flush_to_ldisc(struct work_struct *work) | |||
3607 | tty_buffer_free(tty, tbuf); | 3637 | tty_buffer_free(tty, tbuf); |
3608 | continue; | 3638 | continue; |
3609 | } | 3639 | } |
3640 | /* Ldisc or user is trying to flush the buffers | ||
3641 | we are feeding to the ldisc, stop feeding the | ||
3642 | line discipline as we want to empty the queue */ | ||
3643 | if (test_bit(TTY_FLUSHPENDING, &tty->flags)) | ||
3644 | break; | ||
3610 | if (!tty->receive_room) { | 3645 | if (!tty->receive_room) { |
3611 | schedule_delayed_work(&tty->buf.work, 1); | 3646 | schedule_delayed_work(&tty->buf.work, 1); |
3612 | break; | 3647 | break; |
@@ -3620,8 +3655,17 @@ static void flush_to_ldisc(struct work_struct *work) | |||
3620 | disc->receive_buf(tty, char_buf, flag_buf, count); | 3655 | disc->receive_buf(tty, char_buf, flag_buf, count); |
3621 | spin_lock_irqsave(&tty->buf.lock, flags); | 3656 | spin_lock_irqsave(&tty->buf.lock, flags); |
3622 | } | 3657 | } |
3658 | /* Restore the queue head */ | ||
3623 | tty->buf.head = head; | 3659 | tty->buf.head = head; |
3624 | } | 3660 | } |
3661 | /* We may have a deferred request to flush the input buffer, | ||
3662 | if so pull the chain under the lock and empty the queue */ | ||
3663 | if (test_bit(TTY_FLUSHPENDING, &tty->flags)) { | ||
3664 | __tty_buffer_flush(tty); | ||
3665 | clear_bit(TTY_FLUSHPENDING, &tty->flags); | ||
3666 | wake_up(&tty->read_wait); | ||
3667 | } | ||
3668 | clear_bit(TTY_FLUSHING, &tty->flags); | ||
3625 | spin_unlock_irqrestore(&tty->buf.lock, flags); | 3669 | spin_unlock_irqrestore(&tty->buf.lock, flags); |
3626 | 3670 | ||
3627 | tty_ldisc_deref(disc); | 3671 | tty_ldisc_deref(disc); |
diff --git a/include/linux/tty.h b/include/linux/tty.h index 691a1748d9d2..6570719eafdf 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h | |||
@@ -274,6 +274,8 @@ struct tty_struct { | |||
274 | #define TTY_PTY_LOCK 16 /* pty private */ | 274 | #define TTY_PTY_LOCK 16 /* pty private */ |
275 | #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ | 275 | #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ |
276 | #define TTY_HUPPED 18 /* Post driver->hangup() */ | 276 | #define TTY_HUPPED 18 /* Post driver->hangup() */ |
277 | #define TTY_FLUSHING 19 /* Flushing to ldisc in progress */ | ||
278 | #define TTY_FLUSHPENDING 20 /* Queued buffer flush pending */ | ||
277 | 279 | ||
278 | #define TTY_WRITE_FLUSH(tty) tty_write_flush((tty)) | 280 | #define TTY_WRITE_FLUSH(tty) tty_write_flush((tty)) |
279 | 281 | ||