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-2-matthias.goergens@gmail.com>
Date: Wed,  5 Aug 2026 22:10:24 +0800
From: Matthias Goergens <matthias.goergens@...il.com>
To: musl@...ts.openwall.com
Cc: Matthias Goergens <matthias.goergens@...il.com>
Subject: [PATCH 01/10] regression: test fseeko SEEK_CUR offset underflow

With unread buffered input, fseeko must reject a relative offset
whose buffer adjustment would overflow off_t, fail with EOVERFLOW
without calling the underlying seek or discarding the buffer, and
pass exact boundary offsets through unchanged.

Covered by the musl patch "stdio: avoid overflow adjusting
relative seek offset".
---
 src/regression/fseeko-seek-cur-underflow.c | 113 +++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 src/regression/fseeko-seek-cur-underflow.c

diff --git a/src/regression/fseeko-seek-cur-underflow.c b/src/regression/fseeko-seek-cur-underflow.c
new file mode 100644
index 0000000..c9fdab9
--- /dev/null
+++ b/src/regression/fseeko-seek-cur-underflow.c
@@ -0,0 +1,113 @@
+// fseeko must fail with EOVERFLOW when adjusting a relative SEEK_CUR
+// offset by the unread buffered input would overflow off_t.
+// musl subtracted the buffer length without checking, so a valid
+// LLONG_MIN offset wrapped and reached the underlying seek.
+#define _GNU_SOURCE
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include "test.h"
+
+struct state {
+	int read_done;
+	int seek_calls;
+	off_t seen_offset;
+};
+
+static ssize_t read_cookie(void *cookie, char *buf, size_t size)
+{
+	struct state *st = cookie;
+	if (st->read_done)
+		return 0;
+	st->read_done = 1;
+	if (size > 6)
+		size = 6;
+	memcpy(buf, "abcdef", size);
+	return size;
+}
+
+static int seek_cookie(void *cookie, off_t *off, int whence)
+{
+	struct state *st = cookie;
+	if (whence != SEEK_CUR)
+		return -1;
+	st->seek_calls++;
+	st->seen_offset = *off;
+	return 0;
+}
+
+static FILE *make_stream(struct state *st)
+{
+	cookie_io_functions_t io = {
+		.read = read_cookie,
+		.seek = seek_cookie,
+	};
+	FILE *f = fopencookie(st, "r", io);
+	if (f && fgetc(f) != 'a') {
+		fclose(f);
+		return 0;
+	}
+	return f;
+}
+
+int main(void)
+{
+	struct state st = {0};
+	FILE *f = make_stream(&st);
+	int r, c;
+
+	if (!f) {
+		t_error("fopencookie setup failed\n");
+		return t_status;
+	}
+
+	/* five bytes are buffered; LLONG_MIN - 5 is not representable */
+	errno = 0;
+	r = fseeko(f, LLONG_MIN, SEEK_CUR);
+	if (r != -1 || errno != EOVERFLOW)
+		t_error("fseeko(f, LLONG_MIN, SEEK_CUR) = %d (errno %d), want -1/EOVERFLOW\n",
+			r, errno);
+	if (st.seek_calls)
+		t_error("underlying seek was called for the overflowing offset\n");
+	c = fgetc(f);
+	if (c != 'b')
+		t_error("read buffer not preserved after failed seek: got %c, want b\n",
+			c >= 32 && c < 127 ? c : '?');
+	fclose(f);
+
+	/* boundary: LLONG_MIN + 5 must not take the overflow path; the
+	   underlying seek must see exactly LLONG_MIN. musl reports a
+	   successful seek to a negative absolute offset as -1 without
+	   setting errno, so only the callback arguments are checked. */
+	st = (struct state){0};
+	f = make_stream(&st);
+	if (!f) {
+		t_error("fopencookie setup failed\n");
+		return t_status;
+	}
+	errno = 0;
+	r = fseeko(f, LLONG_MIN + 5, SEEK_CUR);
+	if (st.seek_calls != 1 || st.seen_offset != LLONG_MIN)
+		t_error("fseeko(f, LLONG_MIN+5, SEEK_CUR): seek calls %d, offset %lld; want 1, %lld\n",
+			st.seek_calls, (long long)st.seen_offset, (long long)LLONG_MIN);
+	if (r != -1 || errno != 0)
+		t_error("fseeko(f, LLONG_MIN+5, SEEK_CUR) = %d (errno %d), want -1 with errno unset\n",
+			r, errno);
+	fclose(f);
+
+	/* ordinary offset control: 10 with 5 buffered bytes seeks to 5 */
+	st = (struct state){0};
+	f = make_stream(&st);
+	if (!f) {
+		t_error("fopencookie setup failed\n");
+		return t_status;
+	}
+	r = fseeko(f, 10, SEEK_CUR);
+	if (r != 0 || st.seek_calls != 1 || st.seen_offset != 5)
+		t_error("fseeko(f, 10, SEEK_CUR) = %d, seek calls %d, offset %lld; want 0, 1, 5\n",
+			r, st.seek_calls, (long long)st.seen_offset);
+	fclose(f);
+
+	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.