aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/kern_time.c
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2021-03-08 17:39:06 +0000
committerMark Johnston <markj@FreeBSD.org>2021-03-08 17:39:06 +0000
commit7995dae9d3f58abf38ef0001cee24131f3c9054b (patch)
tree2275b5862adf5f5a17b99f9871f1025e76a8a8d1 /sys/kern/kern_time.c
parent60d12ef952a39581e967a1a608522fdbdedefa01 (diff)
downloadsrc-7995dae9d3f58abf38ef0001cee24131f3c9054b.tar.gz
src-7995dae9d3f58abf38ef0001cee24131f3c9054b.zip
posix timers: Improve the overrun calculation
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
Diffstat (limited to 'sys/kern/kern_time.c')
-rw-r--r--sys/kern/kern_time.c35
1 files changed, 28 insertions, 7 deletions
diff --git a/sys/kern/kern_time.c b/sys/kern/kern_time.c
index 3e85f8e1d6ec..44f6b4ad07f2 100644
--- a/sys/kern/kern_time.c
+++ b/sys/kern/kern_time.c
@@ -1603,6 +1603,13 @@ 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)
@@ -1610,6 +1617,7 @@ realtimer_expire(void *arg)
struct timespec cts, ts;
struct timeval tv;
struct itimer *it;
+ uint64_t interval, now, overruns, value;
it = (struct itimer *)arg;
@@ -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 ? */