|
|
Message-ID: <20260805141033.841558-11-matthias.goergens@gmail.com>
Date: Wed, 5 Aug 2026 22:10:33 +0800
From: Matthias Goergens <matthias.goergens@...il.com>
To: musl@...ts.openwall.com
Cc: Matthias Goergens <matthias.goergens@...il.com>
Subject: [PATCH 10/10] regression: test TZif version and footer parsing
The TZif v1 version byte is NUL, so conforming v1 files must
parse, and a POSIX footer must only be accepted exactly at the end
of the selected data block, never by scanning into binary
transition data or before the mapping. Each case runs in a child
so a crashing implementation fails the case instead of the test.
Covered by the musl patch "time: fix TZif version and footer
parsing".
---
src/regression/tzif-version-footer.c | 140 +++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 src/regression/tzif-version-footer.c
diff --git a/src/regression/tzif-version-footer.c b/src/regression/tzif-version-footer.c
new file mode 100644
index 0000000..65f28f9
--- /dev/null
+++ b/src/regression/tzif-version-footer.c
@@ -0,0 +1,140 @@
+// RFC 9636 encodes the TZif v1 version byte as NUL, but musl treated
+// only the literal byte '1' as v1, reading every conforming v1 file as
+// though a second header followed its data. Footer detection also
+// scanned backwards from any terminal newline without a lower bound,
+// so it could run before the mapping or mistake a newline in binary
+// transition data for the footer delimiter. Valid v1 files must parse,
+// and a footer must only be accepted exactly at the end of the
+// selected data block.
+// Each case runs in a child so a crashing implementation fails the
+// case instead of aborting the whole test.
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include "test.h"
+
+static void put_header(unsigned char *p, char version)
+{
+ memcpy(p, "TZif", 4);
+ p[4] = version;
+ p[35] = 1; /* one transition */
+ p[39] = 1; /* one ttinfo record */
+ p[43] = 4; /* four abbreviation bytes */
+}
+
+static size_t put_block(unsigned char *p, int wide, int binary_newline)
+{
+ size_t n = wide ? 8 : 4;
+
+ /* a transition at 10 deliberately contains a binary newline byte */
+ p[n-1] = binary_newline ? 10 : 11;
+ p[n] = 0;
+ memcpy(p+n+7, "UTC", 4);
+ return n + 1 + 6 + 4;
+}
+
+static int write_tzif(const char *path, int mode)
+{
+ unsigned char data[160] = {0};
+ size_t size;
+ FILE *f;
+
+ if (mode < 2) {
+ /* minimal TZif v1: NUL version byte, no transitions */
+ memcpy(data, "TZif", 4);
+ data[39] = 1; /* one ttinfo record */
+ data[43] = 4; /* four abbreviation bytes */
+ memcpy(data+50, "UTC", 4);
+ size = 54;
+ } else {
+ put_header(data, '2');
+ size = 44 + put_block(data+44, 0, mode != 4);
+ put_header(data+size, '2');
+ size += 44 + put_block(data+size+44, 1, mode != 4);
+ }
+ if (mode == 1 || mode == 3 || mode == 4)
+ data[size++] = '\n'; /* terminal newline without a footer */
+ else if (mode == 2) {
+ memcpy(data + size, "\nXST-2\n", 7);
+ size += 7;
+ }
+ f = fopen(path, "wb");
+ if (!f)
+ return -1;
+ if (fwrite(data, 1, size, f) != size) {
+ fclose(f);
+ return -1;
+ }
+ return fclose(f);
+}
+
+static const char *const mode_name[] = {
+ "valid footerless TZif v1",
+ "TZif v1 with invalid terminal newline",
+ "genuine TZif v2 with POSIX footer",
+ "malformed TZif v2 with newline in binary data",
+ "malformed TZif v2 with terminal newline only",
+};
+
+static int run_mode(int mode)
+{
+ char path[] = "/tmp/libc-test-tzif-XXXXXX";
+ time_t t = 0;
+ struct tm out;
+ const char *want_zone = mode == 2 ? "XST" : "UTC";
+ long want_offset = mode == 2 ? 7200 : 0;
+ int fd = mkstemp(path);
+
+ if (fd < 0 || close(fd) || write_tzif(path, mode)) {
+ fprintf(stderr, "mode %d: setup failed\n", mode);
+ return 65;
+ }
+ if (setenv("TZ", path, 1))
+ return 66;
+ tzset();
+ unlink(path);
+ if (!localtime_r(&t, &out))
+ return 67;
+ if (!out.tm_zone || strcmp(out.tm_zone, want_zone) ||
+ !tzname[0] || strcmp(tzname[0], want_zone) ||
+ out.tm_gmtoff != want_offset || out.tm_isdst) {
+ fprintf(stderr, "mode %d (%s): zone=%s tzname=%s gmtoff=%ld isdst=%d, want %s/%ld/0\n",
+ mode, mode_name[mode],
+ out.tm_zone ? out.tm_zone : "(null)",
+ tzname[0] ? tzname[0] : "(null)",
+ out.tm_gmtoff, out.tm_isdst, want_zone, want_offset);
+ return 68;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ int mode;
+
+ for (mode = 0; mode < 5; mode++) {
+ pid_t pid = fork();
+ int status;
+
+ if (pid == 0)
+ _exit(run_mode(mode));
+ if (pid < 0 || waitpid(pid, &status, 0) != pid) {
+ t_error("fork/waitpid failed for mode %d\n", mode);
+ continue;
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ if (WIFSIGNALED(status))
+ t_error("%s: killed by signal %d\n",
+ mode_name[mode], WTERMSIG(status));
+ else
+ t_error("%s: failed with status %d\n",
+ mode_name[mode],
+ WIFEXITED(status) ? WEXITSTATUS(status) : status);
+ }
+ }
+ 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.