[possible bug] missed wakeup in do_sigtimedwait()?

From: Al Viro
Date: Sat Sep 04 2021 - 10:45:12 EST


do_sigtimedwait():
spin_lock_irq(&tsk->sighand->siglock);
sig = dequeue_signal(tsk, &mask, info);
nope, nothing posted yet
if (!sig && timeout) {
/*
* None ready, temporarily unblock those we're interested
* while we are sleeping in so that we'll be awakened when
* they arrive. Unblocking is always fine, we can avoid
* set_current_blocked().
*/
tsk->real_blocked = tsk->blocked;
sigandsets(&tsk->blocked, &tsk->blocked, &mask);
recalc_sigpending();
spin_unlock_irq(&tsk->sighand->siglock);
... and now somebody sends us a signal. signal_wake_up() does nothing,
since we are still in TASK_RUNNING at that point

__set_current_state(TASK_INTERRUPTIBLE);
ret = freezable_schedule_hrtimeout_range(to, tsk->timer_slack_ns,
HRTIMER_MODE_REL);
... and we go to sleep for the duration of timeout or until the next
signal to arrive.

spin_lock_irq(&tsk->sighand->siglock);
__set_task_blocked(tsk, &tsk->real_blocked);
sigemptyset(&tsk->real_blocked);
sig = dequeue_signal(tsk, &mask, info);
... now we finally dequeue the sucker that had been pending through the
entire timeout period.

}
spin_unlock_irq(&tsk->sighand->siglock);

Looks like that __set_current_state() should've been done before dropping
the siglock. Am I missing something subtle here? It's not a terribly
wide window, but it's not impossible to hit e.g. on KVM and it does look
like a missed wakeup problem... For that matter, spin_unlock_irq() might
run irq handlers, so it's not impossible to hit on the real hardware either.