Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: Fix unsafe vt paste_selection()

Convert the tty_buffer_flush() exclusion mechanism to a
public interface - tty_buffer_lock/unlock_exclusive() - and use
the interface to safely write the paste selection to the line
discipline.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Peter Hurley and committed by
Greg Kroah-Hartman
a7c8d58c 47aa658a

+56 -16
+48 -13
drivers/tty/tty_buffer.c
··· 30 30 31 31 32 32 /** 33 + * tty_buffer_lock_exclusive - gain exclusive access to buffer 34 + * tty_buffer_unlock_exclusive - release exclusive access 35 + * 36 + * @port - tty_port owning the flip buffer 37 + * 38 + * Guarantees safe use of the line discipline's receive_buf() method by 39 + * excluding the buffer work and any pending flush from using the flip 40 + * buffer. Data can continue to be added concurrently to the flip buffer 41 + * from the driver side. 42 + * 43 + * On release, the buffer work is restarted if there is data in the 44 + * flip buffer 45 + */ 46 + 47 + void tty_buffer_lock_exclusive(struct tty_port *port) 48 + { 49 + struct tty_bufhead *buf = &port->buf; 50 + 51 + atomic_inc(&buf->priority); 52 + mutex_lock(&buf->lock); 53 + } 54 + 55 + void tty_buffer_unlock_exclusive(struct tty_port *port) 56 + { 57 + struct tty_bufhead *buf = &port->buf; 58 + int restart; 59 + 60 + restart = buf->head->commit != buf->head->read; 61 + 62 + atomic_dec(&buf->priority); 63 + mutex_unlock(&buf->lock); 64 + if (restart) 65 + queue_work(system_unbound_wq, &buf->work); 66 + } 67 + 68 + /** 33 69 * tty_buffer_space_avail - return unused buffer space 34 70 * @port - tty_port owning the flip buffer 35 71 * ··· 194 158 * being processed by flush_to_ldisc then we defer the processing 195 159 * to that function 196 160 * 197 - * Locking: takes flush_mutex to ensure single-threaded flip buffer 161 + * Locking: takes buffer lock to ensure single-threaded flip buffer 198 162 * 'consumer' 199 163 */ 200 164 ··· 204 168 struct tty_bufhead *buf = &port->buf; 205 169 struct tty_buffer *next; 206 170 207 - buf->flushpending = 1; 171 + atomic_inc(&buf->priority); 208 172 209 - mutex_lock(&buf->flush_mutex); 173 + mutex_lock(&buf->lock); 210 174 while ((next = buf->head->next) != NULL) { 211 175 tty_buffer_free(port, buf->head); 212 176 buf->head = next; 213 177 } 214 178 buf->head->read = buf->head->commit; 215 - buf->flushpending = 0; 216 - mutex_unlock(&buf->flush_mutex); 179 + atomic_dec(&buf->priority); 180 + mutex_unlock(&buf->lock); 217 181 } 218 182 219 183 /** ··· 419 383 * 420 384 * The receive_buf method is single threaded for each tty instance. 421 385 * 422 - * Locking: takes flush_mutex to ensure single-threaded flip buffer 386 + * Locking: takes buffer lock to ensure single-threaded flip buffer 423 387 * 'consumer' 424 388 */ 425 389 ··· 438 402 if (disc == NULL) 439 403 return; 440 404 441 - mutex_lock(&buf->flush_mutex); 405 + mutex_lock(&buf->lock); 442 406 443 407 while (1) { 444 408 struct tty_buffer *head = buf->head; 445 409 int count; 446 410 447 - /* Ldisc or user is trying to flush the buffers. */ 448 - if (buf->flushpending) 411 + /* Ldisc or user is trying to gain exclusive access */ 412 + if (atomic_read(&buf->priority)) 449 413 break; 450 414 451 415 count = head->commit - head->read; ··· 462 426 break; 463 427 } 464 428 465 - mutex_unlock(&buf->flush_mutex); 429 + mutex_unlock(&buf->lock); 466 430 467 431 tty_ldisc_deref(disc); 468 432 } ··· 518 482 { 519 483 struct tty_bufhead *buf = &port->buf; 520 484 521 - mutex_init(&buf->flush_mutex); 485 + mutex_init(&buf->lock); 522 486 tty_buffer_reset(&buf->sentinel, 0); 523 487 buf->head = &buf->sentinel; 524 488 buf->tail = &buf->sentinel; 525 489 init_llist_head(&buf->free); 526 490 atomic_set(&buf->memory_used, 0); 527 - buf->flushpending = 0; 491 + atomic_set(&buf->priority, 0); 528 492 INIT_WORK(&buf->work, flush_to_ldisc); 529 493 } 530 -
+3 -1
drivers/tty/vt/selection.c
··· 24 24 #include <linux/selection.h> 25 25 #include <linux/tiocl.h> 26 26 #include <linux/console.h> 27 + #include <linux/tty_flip.h> 27 28 28 29 /* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */ 29 30 #define isspace(c) ((c) == ' ') ··· 347 346 console_unlock(); 348 347 349 348 ld = tty_ldisc_ref_wait(tty); 349 + tty_buffer_lock_exclusive(&vc->port); 350 350 351 - /* FIXME: this is completely unsafe */ 352 351 add_wait_queue(&vc->paste_wait, &wait); 353 352 while (sel_buffer && sel_buffer_lth > pasted) { 354 353 set_current_state(TASK_INTERRUPTIBLE); ··· 364 363 remove_wait_queue(&vc->paste_wait, &wait); 365 364 __set_current_state(TASK_RUNNING); 366 365 366 + tty_buffer_unlock_exclusive(&vc->port); 367 367 tty_ldisc_deref(ld); 368 368 return 0; 369 369 }
+2 -2
include/linux/tty.h
··· 67 67 struct tty_bufhead { 68 68 struct tty_buffer *head; /* Queue head */ 69 69 struct work_struct work; 70 - struct mutex flush_mutex; 71 - unsigned int flushpending:1; 70 + struct mutex lock; 71 + atomic_t priority; 72 72 struct tty_buffer sentinel; 73 73 struct llist_head free; /* Free queue head */ 74 74 atomic_t memory_used; /* In-use buffers excluding free list */
+3
include/linux/tty_flip.h
··· 32 32 return tty_insert_flip_string_fixed_flag(port, chars, TTY_NORMAL, size); 33 33 } 34 34 35 + extern void tty_buffer_lock_exclusive(struct tty_port *port); 36 + extern void tty_buffer_unlock_exclusive(struct tty_port *port); 37 + 35 38 #endif /* _LINUX_TTY_FLIP_H */