Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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.