git: 121740e18515 - stable/13 - kqueue: don't arbitrarily restrict long-past values for NOTE_ABSTIME
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 06 Oct 2021 07:04:37 UTC
The branch stable/13 has been updated by kevans:
URL: https://cgit.FreeBSD.org/src/commit/?id=121740e18515f56f54931c3d0dbbca620175c2a8
commit 121740e18515f56f54931c3d0dbbca620175c2a8
Author: Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2021-09-29 19:55:59 +0000
Commit: Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2021-10-06 07:03:53 +0000
kqueue: don't arbitrarily restrict long-past values for NOTE_ABSTIME
NOTE_ABSTIME values are converted to values relative to boottime in
filt_timervalidate(), and negative values are currently rejected. We
don't reject times in the past in general, so clamp this up to 0 as
needed such that the timer fires immediately rather than imposing what
looks like an arbitrary restriction.
Another possible scenario is that the system clock had to be adjusted
by ~minutes or ~hours and we have less than that in terms of uptime,
making a reasonable short-timeout suddenly invalid. Firing it is still
a valid choice in this scenario so that applications can at least
expect a consistent behavior.
(cherry picked from commit 9c999a259f00b35f0467acd351fea9157ed7e1e4)
(cherry picked from commit 2f4dbe279f6b5eb87ec493d96f6943ffdb603ba0)
---
sys/kern/kern_event.c | 12 +++-
tests/sys/kqueue/libkqueue/timer.c | 114 +++++++++++++++++++++++++++++++++++++
2 files changed, 123 insertions(+), 3 deletions(-)
diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c
index 91895d341184..2e9773ab5701 100644
--- a/sys/kern/kern_event.c
+++ b/sys/kern/kern_event.c
@@ -798,13 +798,13 @@ filt_timervalidate(struct knote *kn, sbintime_t *to)
return (EINVAL);
*to = timer2sbintime(kn->kn_sdata, kn->kn_sfflags);
+ if (*to < 0)
+ return (EINVAL);
if ((kn->kn_sfflags & NOTE_ABSTIME) != 0) {
getboottimebin(&bt);
sbt = bttosbt(bt);
- *to -= sbt;
+ *to = MAX(0, *to - sbt);
}
- if (*to < 0)
- return (EINVAL);
return (0);
}
@@ -815,9 +815,15 @@ filt_timerattach(struct knote *kn)
sbintime_t to;
int error;
+ to = -1;
error = filt_timervalidate(kn, &to);
if (error != 0)
return (error);
+ KASSERT(to > 0 || (kn->kn_flags & EV_ONESHOT) != 0 ||
+ (kn->kn_sfflags & NOTE_ABSTIME) != 0,
+ ("%s: periodic timer has a calculated zero timeout", __func__));
+ KASSERT(to >= 0,
+ ("%s: timer has a calculated negative timeout", __func__));
if (atomic_fetchadd_int(&kq_ncallouts, 1) + 1 > kq_calloutmax) {
atomic_subtract_int(&kq_ncallouts, 1);
diff --git a/tests/sys/kqueue/libkqueue/timer.c b/tests/sys/kqueue/libkqueue/timer.c
index cb22887be276..330c22c62bc5 100644
--- a/tests/sys/kqueue/libkqueue/timer.c
+++ b/tests/sys/kqueue/libkqueue/timer.c
@@ -247,6 +247,117 @@ test_abstime(void)
success();
}
+static void
+test_abstime_epoch(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER (EPOCH), NOTE_ABSTIME)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, NOTE_ABSTIME, 0,
+ NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD;
+ kev.data = 1;
+ kev.fflags = 0;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the event */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+static void
+test_abstime_preboot(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER (PREBOOT), EV_ONESHOT, NOTE_ABSTIME)";
+ struct kevent kev;
+ struct timespec btp;
+ uint64_t end, start, stop;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /*
+ * We'll expire it at just before system boot (roughly) with the hope that
+ * we'll get an ~immediate expiration, just as we do for any value specified
+ * between system boot and now.
+ */
+ start = now();
+ if (clock_gettime(CLOCK_BOOTTIME, &btp) != 0)
+ err(1, "%s", test_id);
+
+ end = start - SEC_TO_US(btp.tv_sec + 1);
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
+ NOTE_ABSTIME | NOTE_USECONDS, end, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_ONESHOT;
+ kev.data = 1;
+ kev.fflags = 0;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ stop = now();
+ if (stop < end)
+ err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);
+ /* Check if the event occurs again */
+ sleep(3);
+ test_no_kevents();
+
+ success();
+}
+
+static void
+test_abstime_postboot(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER (POSTBOOT), EV_ONESHOT, NOTE_ABSTIME)";
+ struct kevent kev;
+ uint64_t end, start, stop;
+ const int timeout_sec = 1;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /*
+ * Set a timer for 1 second ago, it should fire immediately rather than
+ * being rejected.
+ */
+ start = now();
+ end = start - SEC_TO_US(timeout_sec);
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
+ NOTE_ABSTIME | NOTE_USECONDS, end, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_ONESHOT;
+ kev.data = 1;
+ kev.fflags = 0;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ stop = now();
+ if (stop < end)
+ err(1, "too early %jd %jd", (intmax_t)stop, (intmax_t)end);
+ /* Check if the event occurs again */
+ sleep(3);
+ test_no_kevents();
+
+ success();
+}
+
static void
test_update(void)
{
@@ -517,6 +628,9 @@ test_evfilt_timer(void)
test_oneshot();
test_periodic();
test_abstime();
+ test_abstime_epoch();
+ test_abstime_preboot();
+ test_abstime_postboot();
test_update();
test_update_equal();
test_update_expired();