From 86c7d40e7d6036075cecce566c37464346ed2ab2 Mon Sep 17 00:00:00 2001
From: Florian Weimer <fweimer@redhat.com>
Date: Tue, 20 Jan 2015 13:37:40 +0100
Subject: [PATCH] Make time zone file parser more robust [BZ #17715]
2015-01-20 Florian Weimer <fweimer@redhat.com>
[BZ #17715]
* time/tzfile.c (__tzfile_read): Check for large values of
tzh_ttisstdcnt and tzh_ttisgmtcnt. Reject overlong POSIX time
zone specifiers.
* time/tzset.c (__tzset_parse_tz): Guard against large time zone
specifiers.
* timezone/Makefile (tests): Add tst-tzset.
(tst-tzset.out): Dependencies on time zone files.
(tst-tzset-ENV): Set TZDIR.
(testdata/XT%): Copy crafted time zone files.
* timezone/README: Mention crafted time zone files.
* timezone/testdata/XT1, timezone/testdata/XT2,
timezone/testdata/XT3, timezone/testdata/XT4: New time zone test
files.
* timezone/tst-tzset.c: New test.
@@ -15,9 +15,9 @@ Version 2.21
17501, 17506, 17508, 17522, 17555, 17570, 17571, 17572, 17573, 17574,
17582, 17583, 17584, 17585, 17589, 17594, 17601, 17608, 17616, 17625,
17630, 17633, 17634, 17635, 17647, 17653, 17657, 17658, 17664, 17665,
- 17668, 17682, 17717, 17719, 17722, 17723, 17724, 17725, 17732, 17733,
- 17744, 17745, 17746, 17747, 17748, 17775, 17777, 17780, 17781, 17782,
- 17791, 17793, 17796, 17797, 17803, 17806, 17834, 17844, 17848
+ 17668, 17682, 17715, 17717, 17719, 17722, 17723, 17724, 17725, 17732,
+ 17733, 17744, 17745, 17746, 17747, 17748, 17775, 17777, 17780, 17781,
+ 17782, 17791, 17793, 17796, 17797, 17803, 17806, 17834, 17844, 17848
* Port to Altera Nios II has been contributed by Mentor Graphics.
@@ -50,6 +50,12 @@ Version 2.21
infinite loop if the DNS response contained a PTR record of an unexpected
format.
+* The time zone file parser has been made more robust against crafted time
+ zone files, avoiding heap buffer overflows related to the processing of
+ the tzh_ttisstdcnt and tzh_ttisgmtcnt fields, and a stack overflow due to
+ large time zone data files. Overly long time zone specifiers in the TZ
+ variable no longer result in stack overflows and crashes.
+
* The minimum GCC version that can be used to build this version of the GNU
C Library is GCC 4.6. Older GCC versions, and non-GNU compilers, can
still be used to compile programs using the GNU C Library.
@@ -200,6 +200,9 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
num_isstd = (size_t) decode (tzhead.tzh_ttisstdcnt);
num_isgmt = (size_t) decode (tzhead.tzh_ttisgmtcnt);
+ if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types))
+ goto lose;
+
/* For platforms with 64-bit time_t we use the new format if available. */
if (sizeof (time_t) == 8 && trans_width == 4
&& tzhead.tzh_version[0] != '\0')
@@ -270,7 +273,10 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
if (__glibc_unlikely (SIZE_MAX - total_size < tzspec_len))
goto lose;
}
- if (__glibc_unlikely (SIZE_MAX - total_size - tzspec_len < extra))
+ if (__glibc_unlikely (SIZE_MAX - total_size - tzspec_len < extra
+ || tzspec_len >= 256))
+ /* POSIX time zone specifiers are much shorter than 256
+ characters. */
goto lose;
/* Allocate enough memory including the extra block requested by the
@@ -434,6 +440,10 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
goto lose;
tzspec_len = st.st_size - off - 1;
+ if (__glibc_unlikely (tzspec_len == 0 || tzspec_len >= 256))
+ /* POSIX time zone specifiers are much shorter than 256
+ characters. */
+ goto lose;
char *tzstr = alloca (tzspec_len);
if (getc_unlocked (f) != '\n'
|| (__fread_unlocked (tzstr, 1, tzspec_len - 1, f)
@@ -176,11 +176,14 @@ __tzset_parse_tz (tz)
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "";
- /* Get the standard timezone name. */
- char *tzbuf = strdupa (tz);
+ /* POSIX time zone specifiers are much shorter than 256 characters. */
+ char tzbuf[256];
+ if (strlen (tz) > sizeof (tzbuf) - 1)
+ goto out;
+ /* Get the standard timezone name. */
int consumed;
- if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
+ if (sscanf (tz, "%255[A-Za-z]%n", tzbuf, &consumed) != 1)
{
/* Check for the quoted version. */
char *wp = tzbuf;
@@ -227,7 +230,7 @@ __tzset_parse_tz (tz)
/* Get the DST timezone name (if any). */
if (*tz != '\0')
{
- if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
+ if (sscanf (tz, "%255[A-Za-z]%n", tzbuf, &consumed) != 1)
{
/* Check for the quoted version. */
char *wp = tzbuf;
@@ -25,7 +25,7 @@ include ../Makeconfig
extra-objs := scheck.o ialloc.o
others := zdump zic
-tests := test-tz tst-timezone
+tests := test-tz tst-timezone tst-tzset
# pacificnew doesn't compile; if it is to be used, it should be included in
# northamerica.
@@ -90,9 +90,11 @@ $(objpfx)tst-timezone.out: $(addprefix $(testdata)/, \
Australia/Melbourne \
America/Sao_Paulo Asia/Tokyo \
Europe/London)
+$(objpfx)tst-tzset.out: $(addprefix $(testdata)/XT, 1 2 3 4)
test-tz-ENV = TZDIR=$(testdata)
tst-timezone-ENV = TZDIR=$(testdata)
+tst-tzset-ENV = TZDIR=$(testdata)
# Note this must come second in the deps list for $(built-program-cmd) to work.
zic-deps = $(objpfx)zic $(leapseconds) yearistype
@@ -114,6 +116,8 @@ $(testdata)/America/Sao_Paulo: southamerica $(zic-deps)
$(testdata)/Asia/Tokyo: asia $(zic-deps)
$(build-testdata)
+$(testdata)/XT%: testdata/XT%
+ cp $< $@
$(objpfx)tzselect: tzselect.ksh $(common-objpfx)config.make
sed -e 's|/bin/bash|$(BASH)|' \
@@ -15,3 +15,6 @@ version of the tzcode and tzdata packages.
These packages may be found at ftp://ftp.iana.org/tz/releases/. Commentary
should be addressed to tz@iana.org.
+
+The subdirectory testdata contains manually edited data files for
+regression testing purposes.
new file mode 100644
GIT binary patch
literal 127
zcmWHE%1kq2Kn4GS04TzYB+3Y6vq1O}A%;Lk2w{C7Jz#x5AR1vL!~iZJWxxdhA3F~_
literal 0
HcmV?d00001
new file mode 100644
GIT binary patch
literal 127
wcmWHE%1kq2zyQqufdEOA5y)nN@FPM%>O%<Y1L*<l`vK7iBOwNG0VxA60ROxXJ^%m!
literal 0
HcmV?d00001
new file mode 100644
GIT binary patch
literal 127
wcmWHE%1kq2zyJb35k@3Y5Ss<Uj|edaGC~OJ1L*<l`vK7iBOwNG0VxA60KSR`WdHyG
literal 0
HcmV?d00001
new file mode 100644
GIT binary patch
literal 127
wcmWHE%1kq2zyORu5dkDo5T6CYj|edVGC~OJ1L*<l`vK7iBOwNG0VxA60KRGmXaE2J
literal 0
HcmV?d00001
new file mode 100644
@@ -0,0 +1,167 @@
+/* tzset tests with crafted time zone data.
+ Copyright (C) 2015 Free Software Foundation, Inc.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#define _GNU_SOURCE 1
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+static int do_test (void);
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+/* Returns the name of a large TZ file. */
+static char *
+create_tz_file (off64_t size)
+{
+ char *path;
+ int fd = create_temp_file ("tst-tzset-", &path);
+ if (fd < 0)
+ exit (1);
+
+ // Reopen for large-file support.
+ close (fd);
+ fd = open64 (path, O_WRONLY);
+ if (fd < 0)
+ {
+ printf ("open64 (%s) failed: %m\n", path);
+ exit (1);
+ }
+
+ static const char data[] = {
+ 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x58, 0x54, 0x47, 0x00, 0x00, 0x00,
+ 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x04, 0xf8, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x58, 0x54, 0x47, 0x00, 0x00,
+ 0x00, 0x0a, 0x58, 0x54, 0x47, 0x30, 0x0a
+ };
+ ssize_t ret = write (fd, data, sizeof (data));
+ if (ret < 0)
+ {
+ printf ("write failed: %m\n");
+ exit (1);
+ }
+ if ((size_t) ret != sizeof (data))
+ {
+ printf ("Short write\n");
+ exit (1);
+ }
+ if (lseek64 (fd, size, SEEK_CUR) < 0)
+ {
+ printf ("lseek failed: %m\n");
+ close (fd);
+ return NULL;
+ }
+ if (write (fd, "", 1) != 1)
+ {
+ printf ("Single-byte write failed\n");
+ close (fd);
+ return NULL;
+ }
+ if (close (fd) != 0)
+ {
+ printf ("close failed: %m\n");
+ exit (1);
+ }
+ return path;
+}
+
+static void
+test_tz_file (off64_t size)
+{
+ char *path = create_tz_file (size);
+ if (setenv ("TZ", path, 1) < 0)
+ {
+ printf ("setenv failed: %m\n");
+ exit (1);
+ }
+ tzset ();
+ free (path);
+}
+
+static int
+do_test (void)
+{
+ int errors = 0;
+ for (int i = 1; i <= 4; ++i)
+ {
+ char tz[16];
+ snprintf (tz, sizeof (tz), "XT%d", i);
+ if (setenv ("TZ", tz, 1) < 0)
+ {
+ printf ("setenv failed: %m\n");
+ return 1;
+ }
+ tzset ();
+ if (strcmp (tzname[0], tz) == 0)
+ {
+ printf ("Unexpected success for %s\n", tz);
+ ++errors;
+ }
+ }
+
+ /* Large TZ files. */
+
+ /* This will succeed on 64-bit architectures, and fail on 32-bit
+ architectures. It used to crash on 32-bit. */
+ test_tz_file (64 * 1024 * 1024);
+
+ /* This will fail on 64-bit and 32-bit architectures. It used to
+ cause a test timeout on 64-bit and crash on 32-bit if the TZ file
+ open succeeded for some reason (it does not use O_LARGEFILE in
+ regular builds). */
+ test_tz_file (4LL * 1024 * 1024 * 1024 - 6);
+
+ /* Large TZ variable. */
+ {
+ size_t length = 64 * 1024 * 1024;
+ char *value = malloc (length + 1);
+ if (value == NULL)
+ {
+ puts ("malloc failed: %m");
+ return 1;
+ }
+ value[length] = '\0';
+ memset (value, ' ', length);
+ value[0] = 'U';
+ value[1] = 'T';
+ value[2] = 'C';
+ if (setenv ("TZ", value, 1) < 0)
+ {
+ printf ("setenv failed: %m\n");
+ return 1;
+ }
+ tzset ();
+ }
+
+ return errors > 0;
+}
--
2.1.0