Re: signals: Bug or manpage inconsistency?

From: Thomas Gleixner
Date: Tue May 30 2017 - 17:00:50 EST


On Tue, 30 May 2017, Linus Torvalds wrote:
> On Tue, May 30, 2017 at 12:35 PM, Thomas Gleixner <tglx@xxxxxxxxxxxxx> wrote:
> > The reason why I'm looking into that is the silly case with posix interval
> > timers dealing with ignored signals. We have to keep these timers self
> > rearming because nothing rearms them when SIG_IGN is lifted.
>
> Why not do SIG_IGN specially at signal generation time, the way
> SIGCHLD does. If SIG_IGN is set, you re-arm immediately but do not
> actually deliver the signal. It will automatically be re-delivered
> next time around (assuming people have by then installed a real signal
> handler).

The rearming is exactly the issue. Assume the following:

t.interval.tv_nsec = 1;
t.interval.tv_sec = 0;

timer_set(timerid, 0, &t, NULL);

This creates a 1 nanosecond periodic timer, which is silly to begin with,
but allowed. In the normal case this is automatically rate limited by:

expire -> queue signal -> wakeup task -> dequeue signal -> rearm

So the scheduler controls how much CPU this task gets because the signal
must be dequeued to rearm the timer. If it's the only task on the system it
will still hog the CPU, but that's the same as if you do while(1).

In the SIG_IGN case it is not, because we need to rearm automatically. So
with that 1 nsec interval you created a DOS attack because the system is
busy expiring and rearming the timer.

The mitigation we have in place is to rate limit that rearming to one
jiffie, so the DOS won't happen.

But that's just a stupid hack. The proper solution would be to rearm the
timer at the point where the SIG_IGN is replaced or for that matter the
ignored signal is blocked.

I'm fine with the current behaviour vs. blocking the ignored signal, I
still think it's inconsistent, but that could be debated forever.

Though it needs to be documented somewhere proper and the man page of
sigpending() needs to be fixed so the next person looking into this starts
scratching his head and asks the same questions again.

So with the existing blocking ignored signal semantics the DOS is mitigated
as well, because the signal is queued and the rearming happens when the
signal is dequeued as in the normal case above, through unblocking or
sigwait().

Now there is a subtle issue with this. The following code sequence will
stop the timer forever:

block(sig);

timer expires -> signal is queued -> timer is stopped

ignore(sig);

pending signal is discarded

install_handler(sig);
unblock(sig);

Neither the handler install nor the unblocking will restart the timer.

I think I have an idea how to handle both cases proper but I certainly
wanted to have clarity about the semantics before starting.

Thanks,

tglx