|
|
Message-ID: <20260805141033.841558-7-matthias.goergens@gmail.com>
Date: Wed, 5 Aug 2026 22:10:29 +0800
From: Matthias Goergens <matthias.goergens@...il.com>
To: musl@...ts.openwall.com
Cc: Matthias Goergens <matthias.goergens@...il.com>
Subject: [PATCH 06/10] regression: test aio_suspend timeout overflow
A huge relative timeout must saturate the absolute deadline and
keep waiting instead of wrapping into the past, and an invalid
relative timespec must fail with EINVAL before waiting. An
already-completed request still returns without consulting the
timeout.
Covered by the musl patch "aio: avoid timeout overflow in
aio_suspend".
---
src/regression/aio-suspend-timeout-overflow.c | 159 ++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 src/regression/aio-suspend-timeout-overflow.c
diff --git a/src/regression/aio-suspend-timeout-overflow.c b/src/regression/aio-suspend-timeout-overflow.c
new file mode 100644
index 0000000..0497b69
--- /dev/null
+++ b/src/regression/aio-suspend-timeout-overflow.c
@@ -0,0 +1,159 @@
+// aio_suspend converted its relative timeout to an absolute monotonic
+// deadline with unchecked time_t addition, so a huge valid interval
+// overflowed into the past. The fix validates the relative timespec
+// (failing invalid ones with EINVAL) and saturates an unrepresentably
+// remote deadline instead of wrapping it.
+// The pending request is a pipe read that never completes. A huge
+// timeout must still be waiting when SIGALRM arrives 10 ms later; an
+// invalid one must fail immediately with EINVAL. The timer also
+// bounds every wait so a broken implementation fails fast instead of
+// hanging the test.
+#define _GNU_SOURCE
+#include <aio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include "test.h"
+
+static void handler(int sig)
+{
+ (void)sig;
+}
+
+static void arm_timer(void)
+{
+ struct sigaction sa = { .sa_handler = handler };
+ struct itimerval it = { .it_value = { .tv_usec = 10000 } };
+
+ if (sigaction(SIGALRM, &sa, 0) || setitimer(ITIMER_REAL, &it, 0))
+ t_error("failed to arm the interrupt timer\n");
+}
+
+static int pipefds[2] = { -1, -1 };
+static char byte;
+static struct aiocb pending;
+
+static int setup_pending(void)
+{
+ if (pipe(pipefds))
+ return -1;
+ pending = (struct aiocb){
+ .aio_fildes = pipefds[0],
+ .aio_buf = &byte,
+ .aio_nbytes = 1,
+ };
+ /* nothing is ever written: the request stays EINPROGRESS */
+ if (aio_read(&pending))
+ return -1;
+ return 0;
+}
+
+static int suspend(const struct timespec *ts, int timed)
+{
+ const struct aiocb *list[1] = { &pending };
+ int r;
+
+ if (timed)
+ arm_timer();
+ errno = 0;
+ r = aio_suspend(list, 1, ts);
+ if (timed)
+ setitimer(ITIMER_REAL, &(struct itimerval){0}, 0);
+ return r;
+}
+
+/* a huge timeout must wait: SIGALRM interrupts it with EINTR */
+static void check_waits(const char *what, struct timespec ts)
+{
+ int r = suspend(&ts, 1);
+
+ if (r != -1 || errno != EINTR)
+ t_error("%s = %d (errno %d), want -1/EINTR (wait interrupted, not timed out)\n",
+ what, r, errno);
+}
+
+/* an invalid relative timeout must fail with EINVAL before waiting */
+static void check_invalid(const char *what, struct timespec ts)
+{
+ int r = suspend(&ts, 1);
+
+ if (r != -1 || errno != EINVAL)
+ t_error("%s = %d (errno %d), want -1/EINVAL\n", what, r, errno);
+}
+
+/* a genuinely completed request returns even with an invalid timeout */
+static void check_completed(void)
+{
+ char c;
+ int fd = open("/dev/null", O_RDONLY);
+ struct aiocb cb = {
+ .aio_fildes = fd,
+ .aio_buf = &c,
+ .aio_nbytes = 1,
+ };
+ const struct aiocb *list[1] = { &cb };
+ struct timespec ts = { .tv_nsec = 1000000000 };
+ int r;
+
+ if (fd < 0 || aio_read(&cb)) {
+ t_error("aio setup failed\n");
+ if (fd >= 0)
+ close(fd);
+ return;
+ }
+ while (aio_error(&cb) == EINPROGRESS)
+ sched_yield();
+ errno = 0;
+ r = aio_suspend(list, 1, &ts);
+ if (aio_return(&cb) < 0 || close(fd))
+ t_error("aio completion failed\n");
+ if (r != 0)
+ t_error("aio_suspend on a completed request = %d (errno %d), want 0\n",
+ r, errno);
+}
+
+int main(void)
+{
+ struct timespec now;
+ struct timespec ts;
+ int r;
+
+ if (setup_pending()) {
+ t_error("pending aio setup failed\n");
+ return t_status;
+ }
+
+ /* zero timeout control: EAGAIN with nothing completed */
+ ts = (struct timespec){ 0, 0 };
+ r = suspend(&ts, 0);
+ if (r != -1 || errno != EAGAIN)
+ t_error("aio_suspend with zero timeout = %d (errno %d), want -1/EAGAIN\n",
+ r, errno);
+
+ /* the seconds addition overflows outright: saturate and wait */
+ ts = (struct timespec){ INT64_MAX, 0 };
+ check_waits("aio_suspend with TIME_MAX timeout", ts);
+
+ /* the seconds sum is representable but the nanosecond carry overflows */
+ if (clock_gettime(CLOCK_MONOTONIC, &now)) {
+ t_error("clock_gettime failed\n");
+ } else {
+ ts = (struct timespec){ INT64_MAX - now.tv_sec, 999999999 };
+ check_waits("aio_suspend with nanosecond-carry timeout", ts);
+ }
+
+ /* invalid relative timeouts: rejected, not normalized into a wait */
+ ts = (struct timespec){ 0, 1000000000 };
+ check_invalid("aio_suspend with tv_nsec == 1000000000", ts);
+ ts = (struct timespec){ 0, -1 };
+ check_invalid("aio_suspend with tv_nsec == -1", ts);
+
+ check_completed();
+ return t_status;
+}
--
2.55.0
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.