Commit a7b12929 authored by Rabin Vincent's avatar Rabin Vincent Committed by Greg Kroah-Hartman

vt: fix race in vt_waitactive()

pm_restore_console() is called from the suspend/resume path, and this
calls vt_move_to_console(), which calls vt_waitactive().

There's a race in this path which causes the process which requests the
suspend to sleep indefinitely waiting for an event which already
happened:

P1                                      P2
 vt_move_to_console()
  set_console()
    schedule_console_callback()
  vt_waitactive()
    check n == fg_console +1
                                       console_callback()
                                         switch_screen()
                                         vt_event_post() // no waiters

    vt_event_wait() // forever

Fix the race by ensuring we're registered for the event before we check
if it's already completed.
Signed-off-by: default avatarRabin Vincent <rabin.vincent@stericsson.com>
Acked-by: default avatarAlan Cox <alan@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent ed85c604
...@@ -110,16 +110,7 @@ void vt_event_post(unsigned int event, unsigned int old, unsigned int new) ...@@ -110,16 +110,7 @@ void vt_event_post(unsigned int event, unsigned int old, unsigned int new)
wake_up_interruptible(&vt_event_waitqueue); wake_up_interruptible(&vt_event_waitqueue);
} }
/** static void __vt_event_queue(struct vt_event_wait *vw)
* vt_event_wait - wait for an event
* @vw: our event
*
* Waits for an event to occur which completes our vt_event_wait
* structure. On return the structure has wv->done set to 1 for success
* or 0 if some event such as a signal ended the wait.
*/
static void vt_event_wait(struct vt_event_wait *vw)
{ {
unsigned long flags; unsigned long flags;
/* Prepare the event */ /* Prepare the event */
...@@ -129,14 +120,40 @@ static void vt_event_wait(struct vt_event_wait *vw) ...@@ -129,14 +120,40 @@ static void vt_event_wait(struct vt_event_wait *vw)
spin_lock_irqsave(&vt_event_lock, flags); spin_lock_irqsave(&vt_event_lock, flags);
list_add(&vw->list, &vt_events); list_add(&vw->list, &vt_events);
spin_unlock_irqrestore(&vt_event_lock, flags); spin_unlock_irqrestore(&vt_event_lock, flags);
}
static void __vt_event_wait(struct vt_event_wait *vw)
{
/* Wait for it to pass */ /* Wait for it to pass */
wait_event_interruptible(vt_event_waitqueue, vw->done); wait_event_interruptible(vt_event_waitqueue, vw->done);
}
static void __vt_event_dequeue(struct vt_event_wait *vw)
{
unsigned long flags;
/* Dequeue it */ /* Dequeue it */
spin_lock_irqsave(&vt_event_lock, flags); spin_lock_irqsave(&vt_event_lock, flags);
list_del(&vw->list); list_del(&vw->list);
spin_unlock_irqrestore(&vt_event_lock, flags); spin_unlock_irqrestore(&vt_event_lock, flags);
} }
/**
* vt_event_wait - wait for an event
* @vw: our event
*
* Waits for an event to occur which completes our vt_event_wait
* structure. On return the structure has wv->done set to 1 for success
* or 0 if some event such as a signal ended the wait.
*/
static void vt_event_wait(struct vt_event_wait *vw)
{
__vt_event_queue(vw);
__vt_event_wait(vw);
__vt_event_dequeue(vw);
}
/** /**
* vt_event_wait_ioctl - event ioctl handler * vt_event_wait_ioctl - event ioctl handler
* @arg: argument to ioctl * @arg: argument to ioctl
...@@ -177,10 +194,14 @@ int vt_waitactive(int n) ...@@ -177,10 +194,14 @@ int vt_waitactive(int n)
{ {
struct vt_event_wait vw; struct vt_event_wait vw;
do { do {
if (n == fg_console + 1)
break;
vw.event.event = VT_EVENT_SWITCH; vw.event.event = VT_EVENT_SWITCH;
vt_event_wait(&vw); __vt_event_queue(&vw);
if (n == fg_console + 1) {
__vt_event_dequeue(&vw);
break;
}
__vt_event_wait(&vw);
__vt_event_dequeue(&vw);
if (vw.done == 0) if (vw.done == 0)
return -EINTR; return -EINTR;
} while (vw.event.newev != n); } while (vw.event.newev != n);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment