Two fixes for the timer wheel:

 - A timer which is already expired at enqueue time can set the
   base->next_expiry value backwards. As a consequence base->clk can be set
   back as well. This can lead to timers expiring early. Add a sanity check
   to prevent this.

 - When a timer is queued with an expiry time beyond the wheel capacity
   then it should be queued in the bucket of the last wheel level which is
   expiring last. The code adjusts expiry time to the maximum wheel
   capacity, which is only correct when the wheel clock is 0. Aside of that
   the check whether the delta is larger than wheel capacity does not check
   the delta, it checks the expiry value itself. As a result timers can
   expire at random.

   Fix this by checking the right variable and adjust expiry time so it
   becomes base->clock plus capacity which places it into the outmost
   bucket in the last wheel level.
timer: Fix wheel index calculation on last level

When an expiration delta falls into the last level of the wheel, that delta
has be compared against the maximum possible delay and reduced to fit in if
necessary.

However instead of comparing the delta against the maximum, the code
compares the actual expiry against the maximum. Then instead of fixing the
delta to fit in, it sets the maximum delta as the expiry value.

This can result in various undesired outcomes, the worst possible one
being a timer expiring 15 days ahead to fire immediately.

Fixes: 500462a9de65 ("timers: Switch to a non-cascading wheel")
Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: stable@vger.kernel.org
Link: https://lkml.kernel.org/r/20200717140551.29076-2-frederic@kernel.org

1 file changed