Skip to content

Commit

Permalink
posix timers: Improve the overrun calculation
Browse files Browse the repository at this point in the history
timer_settime(2) may be used to configure a timeout in the past.  If
the timer is also periodic, we also try to compute the number of timer
overruns that occurred between the initial timeout and the time at which
the timer fired.  This is done in a loop which iterates once per period
between the initial timeout and now.  If the period is small and the
initial timeout was a long time ago, this loop can take forever to run,
so the system is effectively DOSed.

Replace the loop with a more direct calculation of
(now - initial timeout) / period to compute the number of overruns.

Reported by:	syzkaller
Reviewed by:	kib
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D29093
  • Loading branch information
markjdb committed Mar 8, 2021
1 parent 60d12ef commit 7995dae
Showing 1 changed file with 28 additions and 7 deletions.
35 changes: 28 additions & 7 deletions sys/kern/kern_time.c
Original file line number Diff line number Diff line change
Expand Up @@ -1603,13 +1603,21 @@ itimespecfix(struct timespec *ts)
return (0);
}

#define timespectons(tsp) \
((uint64_t)(tsp)->tv_sec * 1000000000 + (tsp)->tv_nsec)
#define timespecfromns(ns) (struct timespec){ \
.tv_sec = (ns) / 1000000000, \
.tv_nsec = (ns) % 1000000000 \
}

/* Timeout callback for realtime timer */
static void
realtimer_expire(void *arg)
{
struct timespec cts, ts;
struct timeval tv;
struct itimer *it;
uint64_t interval, now, overruns, value;

it = (struct itimer *)arg;

Expand All @@ -1620,14 +1628,27 @@ realtimer_expire(void *arg)
timespecadd(&it->it_time.it_value,
&it->it_time.it_interval,
&it->it_time.it_value);
while (timespeccmp(&cts, &it->it_time.it_value, >=)) {
if (it->it_overrun < INT_MAX)
it->it_overrun++;
else

interval = timespectons(&it->it_time.it_interval);
value = timespectons(&it->it_time.it_value);
now = timespectons(&cts);

if (now >= value) {
/*
* We missed at least one period.
*/
overruns = howmany(now - value + 1, interval);
if (it->it_overrun + overruns >=
it->it_overrun &&
it->it_overrun + overruns <= INT_MAX) {
it->it_overrun += (int)overruns;
} else {
it->it_overrun = INT_MAX;
it->it_ksi.ksi_errno = ERANGE;
timespecadd(&it->it_time.it_value,
&it->it_time.it_interval,
&it->it_time.it_value);
}
value =
now + interval - (now - value) % interval;
it->it_time.it_value = timespecfromns(value);
}
} else {
/* single shot timer ? */
Expand Down

0 comments on commit 7995dae

Please sign in to comment.