use-after-free races have been seen due to the workqueue timer in the tty structure going off after the tty was freed. Fix that up by adding a new workqueue function to synchronously delete the timer, use that in the tty layer. Also add a might_sleep() check to flush_workqueue(). It usually will not sleep, so the check will accelerate bug hunting. And change the flush_workqueue() sleep to be uninterruptible. We worry that delivery of a signal may cause the wait to return too early. 25-akpm/drivers/char/tty_io.c | 9 ++++++++- 25-akpm/include/linux/workqueue.h | 10 ++++++++++ 25-akpm/kernel/workqueue.c | 4 +++- 3 files changed, 21 insertions(+), 2 deletions(-) diff -puN drivers/char/tty_io.c~tty-shutdown-race-fix drivers/char/tty_io.c --- 25/drivers/char/tty_io.c~tty-shutdown-race-fix Wed Apr 2 16:14:12 2003 +++ 25-akpm/drivers/char/tty_io.c Wed Apr 2 16:20:36 2003 @@ -1286,7 +1286,14 @@ static void release_dev(struct file * fi } /* - * Make sure that the tty's task queue isn't activated. + * Prevent flush_to_ldisc() from rescheduling the work for later. Then + * kill any delayed work. + */ + clear_bit(TTY_DONT_FLIP, &tty->flags); + cancel_delayed_work(&tty->flip.work); + + /* + * Wait for ->hangup_work and ->flip.work handlers to terminate */ flush_scheduled_work(); diff -puN include/linux/workqueue.h~tty-shutdown-race-fix include/linux/workqueue.h --- 25/include/linux/workqueue.h~tty-shutdown-race-fix Wed Apr 2 16:16:17 2003 +++ 25-akpm/include/linux/workqueue.h Wed Apr 2 16:18:58 2003 @@ -63,5 +63,15 @@ extern int current_is_keventd(void); extern void init_workqueues(void); +/* + * Kill off a pending schedule_delayed_work(). Note that the work callback + * function may still be running on return from cancel_delayed_work(). Run + * flush_scheduled_work() to wait on it. + */ +static inline int cancel_delayed_work(struct work_struct *work) +{ + return del_timer_sync(&work->timer); +} + #endif diff -puN kernel/workqueue.c~tty-shutdown-race-fix kernel/workqueue.c --- 25/kernel/workqueue.c~tty-shutdown-race-fix Wed Apr 2 16:21:04 2003 +++ 25-akpm/kernel/workqueue.c Wed Apr 2 16:31:08 2003 @@ -231,6 +231,8 @@ void flush_workqueue(struct workqueue_st struct cpu_workqueue_struct *cwq; int cpu; + might_sleep(); + for (cpu = 0; cpu < NR_CPUS; cpu++) { if (!cpu_online(cpu)) continue; @@ -246,7 +248,7 @@ void flush_workqueue(struct workqueue_st * Wait for helper thread(s) to finish up * the queue: */ - set_task_state(current, TASK_INTERRUPTIBLE); + set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&cwq->work_done, &wait); if (atomic_read(&cwq->nr_queued)) schedule(); _