From f9765c9c1acbd42c074841c96c0e84d32ad1b1a1 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Wed, 29 Sep 2021 14:55:59 -0500 Subject: [PATCH] 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 af611281ee6..d6f4516a265 100644 --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -721,13 +721,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); } @@ -739,9 +739,15 @@ filt_timerattach(struct knote *kn) unsigned int ncallouts; 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__)); do { ncallouts = kq_ncallouts; diff --git a/tests/sys/kqueue/libkqueue/timer.c b/tests/sys/kqueue/libkqueue/timer.c index 1861030d697..81ab364b796 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() test_oneshot(); test_periodic(); test_abstime(); + test_abstime_epoch(); + test_abstime_preboot(); + test_abstime_postboot(); test_update(); test_update_equal(); test_update_expired(); -- 2.45.0