Make time zone file parser more robust [BZ #17715]

This commit is contained in:
Florian Weimer 2015-04-24 17:34:47 +02:00
parent ed159672eb
commit 42261ad731
11 changed files with 468 additions and 226 deletions

View File

@ -1,3 +1,30 @@
2015-04-24 Florian Weimer <fweimer@redhat.com>
[BZ #17715]
* time/tzfile.c (__tzfile_read): Check for large values of
tzh_ttisstdcnt and tzh_ttisgmtcnt. Use malloc instead of alloca.
* time/tzset.c (__tzstring_len): New function, based on the old
__tzstring function.
(__tzstring): Call __tzstring_len.
(parse_tzname): New helper function extracted from
__tzset_parse_tz. Call __tzstring_len, without making a copy of
the input string.
(parse_offset): New helper function extracted from
__tzset_parse_tz. Replace switch with fallthrough with
initialization before sscanf.
(parse_rule): Likewise.
(__tzset_parse_tz): Rewrite using the new helper functions. Use
new-style function definition.
* 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.
2015-04-24 Florian Weimer <fweimer@redhat.com>
* Makeconfig (+gccwarn): Remove -Winline.

18
NEWS
View File

@ -11,12 +11,12 @@ Version 2.22
4719, 6792, 13064, 14094, 14841, 14906, 15319, 15467, 15790, 15969, 16351,
16512, 16560, 16783, 16850, 17090, 17195, 17269, 17523, 17542, 17569,
17588, 17596, 17620, 17621, 17628, 17631, 17711, 17776, 17779, 17792,
17836, 17912, 17916, 17930, 17932, 17944, 17949, 17964, 17965, 17967,
17969, 17978, 17987, 17991, 17996, 17998, 17999, 18019, 18020, 18029,
18030, 18032, 18036, 18038, 18039, 18042, 18043, 18046, 18047, 18068,
18080, 18093, 18100, 18104, 18110, 18111, 18128, 18138, 18185, 18197,
18206, 18210, 18211, 18247, 18287.
17588, 17596, 17620, 17621, 17628, 17631, 17711, 17715, 17776, 17779,
17792, 17836, 17912, 17916, 17930, 17932, 17944, 17949, 17964, 17965,
17967, 17969, 17978, 17987, 17991, 17996, 17998, 17999, 18019, 18020,
18029, 18030, 18032, 18036, 18038, 18039, 18042, 18043, 18046, 18047,
18068, 18080, 18093, 18100, 18104, 18110, 18111, 18128, 18138, 18185,
18197, 18206, 18210, 18211, 18247, 18287.
* Cache information can be queried via sysconf() function on s390 e.g. with
_SC_LEVEL1_ICACHE_SIZE as argument.
@ -28,6 +28,12 @@ Version 2.22
potentially arbitrary code execution, using crafted, but syntactically
valid DNS responses. (CVE-2015-1781)
* 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.
* A powerpc and powerpc64 optimization for TLS, similar to TLS descriptors
for LD and GD on x86 and x86-64, has been implemented. You will need
binutils-2.24 or later to enable this optimization.

View File

@ -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')
@ -434,13 +437,21 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
goto lose;
tzspec_len = st.st_size - off - 1;
char *tzstr = alloca (tzspec_len);
if (tzspec_len == 0)
goto lose;
char *tzstr = malloc (tzspec_len);
if (tzstr == NULL)
goto lose;
if (getc_unlocked (f) != '\n'
|| (__fread_unlocked (tzstr, 1, tzspec_len - 1, f)
!= tzspec_len - 1))
{
free (tzstr);
goto lose;
}
tzstr[tzspec_len - 1] = '\0';
tzspec = __tzstring (tzstr);
free (tzstr);
}
/* Don't use an empty TZ string. */

View File

@ -18,6 +18,7 @@
#include <ctype.h>
#include <errno.h>
#include <bits/libc-lock.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -82,15 +83,14 @@ struct tzstring_l
static struct tzstring_l *tzstring_list;
/* Allocate a permanent home for S. It will never be moved or deallocated,
but may share space with other strings.
Don't modify the returned string. */
char *
__tzstring (const char *s)
/* Allocate a permanent home for the first LEN characters of S. It
will never be moved or deallocated, but may share space with other
strings. Don't modify the returned string. */
static char *
__tzstring_len (const char *s, size_t len)
{
char *p;
struct tzstring_l *t, *u, *new;
size_t len = strlen (s);
/* Walk the list and look for a match. If this string is the same
as the end of an already-allocated string, it can share space. */
@ -98,7 +98,7 @@ __tzstring (const char *s)
if (len <= t->len)
{
p = &t->data[t->len - len];
if (strcmp (s, p) == 0)
if (memcmp (s, p, len) == 0)
return p;
}
@ -109,7 +109,8 @@ __tzstring (const char *s)
new->next = NULL;
new->len = len;
strcpy (new->data, s);
memcpy (new->data, s, len);
new->data[len] = '\0';
if (u)
u->next = new;
@ -118,6 +119,15 @@ __tzstring (const char *s)
return new->data;
}
/* Allocate a permanent home for S. It will never be moved or
deallocated, but may share space with other strings. Don't modify
the returned string. */
char *
__tzstring (const char *s)
{
return __tzstring_len (s, strlen (s));
}
/* Maximum length of a timezone name. tzset_internal keeps this up to date
(never decreasing it) when ! __use_tzfile.
@ -164,142 +174,85 @@ compute_offset (unsigned int ss, unsigned int mm, unsigned int hh)
return min (ss, 59) + min (mm, 59) * 60 + min (hh, 24) * 60 * 60;
}
/* Parse the POSIX TZ-style string. */
void
__tzset_parse_tz (tz)
const char *tz;
/* Parses the time zone name at *TZP, and writes a pointer to an
interned string to tz_rules[WHICHRULE].name. On success, advances
*TZP, and returns true. Returns false otherwise. */
static bool
parse_tzname (const char **tzp, int whichrule)
{
unsigned short int hh, mm, ss;
/* Clear out old state and reset to unnamed UTC. */
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "";
/* Get the standard timezone name. */
char *tzbuf = strdupa (tz);
int consumed;
if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
const char *start = *tzp;
const char *p = start;
while (('a' <= *p && *p <= 'z')
|| ('A' <= *p && *p <= 'Z'))
++p;
size_t len = p - start;
if (len < 3)
{
/* Check for the quoted version. */
char *wp = tzbuf;
if (__glibc_unlikely (*tz++ != '<'))
goto out;
while (isalnum (*tz) || *tz == '+' || *tz == '-')
*wp++ = *tz++;
if (__glibc_unlikely (*tz++ != '>' || wp - tzbuf < 3))
goto out;
*wp = '\0';
p = *tzp;
if (__glibc_unlikely (*p++ != '<'))
return false;
start = p;
while (('a' <= *p && *p <= 'z')
|| ('A' <= *p && *p <= 'Z')
|| ('0' <= *p && *p <= '9')
|| *p == '+' || *p == '-')
++p;
len = p - start;
if (*p++ != '>' || len < 3)
return false;
}
tz_rules[whichrule].name = __tzstring_len (start, len);
*tzp = p;
return true;
}
else if (__glibc_unlikely (consumed < 3))
goto out;
else
tz += consumed;
tz_rules[0].name = __tzstring (tzbuf);
/* Figure out the standard offset from UTC. */
if (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz)))
goto out;
if (*tz == '-' || *tz == '+')
tz_rules[0].offset = *tz++ == '-' ? 1L : -1L;
else
tz_rules[0].offset = -1L;
switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
&hh, &consumed, &mm, &consumed, &ss, &consumed))
/* Parses the time zone offset at *TZP, and writes it to
tz_rules[WHICHRULE].offset. Returns true if the parse was
successful. */
static bool
parse_offset (const char **tzp, int whichrule)
{
default:
const char *tz = *tzp;
if (whichrule == 0
&& (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz))))
return false;
long sign;
if (*tz == '-' || *tz == '+')
sign = *tz++ == '-' ? 1L : -1L;
else
sign = -1L;
*tzp = tz;
unsigned short int hh;
unsigned short mm = 0;
unsigned short ss = 0;
int consumed = 0;
if (sscanf (tz, "%hu%n:%hu%n:%hu%n",
&hh, &consumed, &mm, &consumed, &ss, &consumed) > 0)
tz_rules[whichrule].offset = sign * compute_offset (ss, mm, hh);
else
/* Nothing could be parsed. */
if (whichrule == 0)
{
/* Standard time defaults to offset zero. */
tz_rules[0].offset = 0;
goto out;
case 1:
mm = 0;
case 2:
ss = 0;
case 3:
break;
return false;
}
tz_rules[0].offset *= compute_offset (ss, mm, hh);
tz += consumed;
/* Get the DST timezone name (if any). */
if (*tz != '\0')
{
if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
{
/* Check for the quoted version. */
char *wp = tzbuf;
const char *rp = tz;
if (__glibc_unlikely (*rp++ != '<'))
/* Punt on name, set up the offsets. */
goto done_names;
while (isalnum (*rp) || *rp == '+' || *rp == '-')
*wp++ = *rp++;
if (__glibc_unlikely (*rp++ != '>' || wp - tzbuf < 3))
/* Punt on name, set up the offsets. */
goto done_names;
*wp = '\0';
tz = rp;
}
else if (__glibc_unlikely (consumed < 3))
/* Punt on name, set up the offsets. */
goto done_names;
else
tz += consumed;
tz_rules[1].name = __tzstring (tzbuf);
/* Figure out the DST offset from GMT. */
if (*tz == '-' || *tz == '+')
tz_rules[1].offset = *tz++ == '-' ? 1L : -1L;
else
tz_rules[1].offset = -1L;
switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
&hh, &consumed, &mm, &consumed, &ss, &consumed))
{
default:
/* Default to one hour later than standard time. */
/* DST defaults to one hour later than standard time. */
tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
break;
case 1:
mm = 0;
case 2:
ss = 0;
case 3:
tz_rules[1].offset *= compute_offset (ss, mm, hh);
tz += consumed;
break;
}
if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
{
/* There is no rule. See if there is a default rule file. */
__tzfile_default (tz_rules[0].name, tz_rules[1].name,
tz_rules[0].offset, tz_rules[1].offset);
if (__use_tzfile)
{
free (old_tz);
old_tz = NULL;
return;
}
}
}
else
{
/* There is no DST. */
tz_rules[1].name = tz_rules[0].name;
tz_rules[1].offset = tz_rules[0].offset;
goto out;
*tzp = tz + consumed;
return true;
}
done_names:
/* Figure out the standard <-> DST rules. */
for (unsigned int whichrule = 0; whichrule < 2; ++whichrule)
/* Parses the standard <-> DST rules at *TZP. Updates
tz_rule[WHICHRULE]. On success, advances *TZP and returns true.
Otherwise, returns false. */
static bool
parse_rule (const char **tzp, int whichrule)
{
const char *tz = *tzp;
tz_rule *tzr = &tz_rules[whichrule];
/* Ignore comma to support string following the incorrect
@ -312,32 +265,32 @@ __tzset_parse_tz (tz)
char *end;
tzr->type = *tz == 'J' ? J1 : J0;
if (tzr->type == J1 && !isdigit (*++tz))
goto out;
return false;
unsigned long int d = strtoul (tz, &end, 10);
if (end == tz || d > 365)
goto out;
return false;
if (tzr->type == J1 && d == 0)
goto out;
return false;
tzr->d = d;
tz = end;
}
else if (*tz == 'M')
{
tzr->type = M;
int consumed;
if (sscanf (tz, "M%hu.%hu.%hu%n",
&tzr->m, &tzr->n, &tzr->d, &consumed) != 3
|| tzr->m < 1 || tzr->m > 12
|| tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
goto out;
return false;
tz += consumed;
}
else if (*tz == '\0')
{
/* Daylight time rules in the U.S. are defined in the
U.S. Code, Title 15, Chapter 6, Subchapter IX - Standard
Time. These dates were established by Congress in the
Energy Policy Act of 2005 [Pub. L. no. 109-58, 119 Stat 594
(2005)].
/* Daylight time rules in the U.S. are defined in the U.S. Code,
Title 15, Chapter 6, Subchapter IX - Standard Time. These
dates were established by Congress in the Energy Policy Act
of 2005 [Pub. L. no. 109-58, 119 Stat 594 (2005)].
Below is the equivalent of "M3.2.0,M11.1.0" [/2 not needed
since 2:00AM is the default]. */
tzr->type = M;
@ -355,32 +308,26 @@ __tzset_parse_tz (tz)
}
}
else
goto out;
return false;
if (*tz != '\0' && *tz != '/' && *tz != ',')
goto out;
return false;
else if (*tz == '/')
{
/* Get the time of day of the change. */
int negative;
++tz;
if (*tz == '\0')
goto out;
return false;
negative = *tz == '-';
tz += negative;
consumed = 0;
switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
&hh, &consumed, &mm, &consumed, &ss, &consumed))
{
default:
hh = 2; /* Default to 2:00 AM. */
case 1:
mm = 0;
case 2:
ss = 0;
case 3:
break;
}
/* Default to 2:00 AM. */
unsigned short hh = 2;
unsigned short mm = 0;
unsigned short ss = 0;
int consumed = 0;
sscanf (tz, "%hu%n:%hu%n:%hu%n",
&hh, &consumed, &mm, &consumed, &ss, &consumed);;
tz += consumed;
tzr->secs = (negative ? -1 : 1) * ((hh * 60 * 60) + (mm * 60) + ss);
}
@ -389,9 +336,53 @@ __tzset_parse_tz (tz)
tzr->secs = 2 * 60 * 60;
tzr->computed_for = -1;
*tzp = tz;
return true;
}
/* Parse the POSIX TZ-style string. */
void
__tzset_parse_tz (const char *tz)
{
/* Clear out old state and reset to unnamed UTC. */
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "";
/* Get the standard timezone name. */
if (parse_tzname (&tz, 0) && parse_offset (&tz, 0))
{
/* Get the DST timezone name (if any). */
if (*tz != '\0')
{
if (parse_tzname (&tz, 1))
{
parse_offset (&tz, 1);
if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
{
/* There is no rule. See if there is a default rule
file. */
__tzfile_default (tz_rules[0].name, tz_rules[1].name,
tz_rules[0].offset, tz_rules[1].offset);
if (__use_tzfile)
{
free (old_tz);
old_tz = NULL;
return;
}
}
}
/* Figure out the standard <-> DST rules. */
if (parse_rule (&tz, 0))
parse_rule (&tz, 1);
}
else
{
/* There is no DST. */
tz_rules[1].name = tz_rules[0].name;
tz_rules[1].offset = tz_rules[0].offset;
}
}
out:
update_vars ();
}

View File

@ -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)|' \

View File

@ -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.

BIN
timezone/testdata/XT1 vendored Normal file

Binary file not shown.

BIN
timezone/testdata/XT2 vendored Normal file

Binary file not shown.

BIN
timezone/testdata/XT3 vendored Normal file

Binary file not shown.

BIN
timezone/testdata/XT4 vendored Normal file

Binary file not shown.

200
timezone/tst-tzset.c Normal file
View File

@ -0,0 +1,200 @@
/* 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 <sys/resource.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)
{
/* Limit the size of the process. Otherwise, some of the tests will
consume a lot of resources. */
{
struct rlimit limit;
if (getrlimit (RLIMIT_AS, &limit) != 0)
{
printf ("getrlimit (RLIMIT_AS) failed: %m\n");
return 1;
}
long target = 512 * 1024 * 1024;
if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target)
{
limit.rlim_cur = 512 * 1024 * 1024;
if (setrlimit (RLIMIT_AS, &limit) != 0)
{
printf ("setrlimit (RLIMIT_AS) failed: %m\n");
return 1;
}
}
}
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 variables. */
{
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 ();
memset (value, '0', length);
value[0] = '<';
value[length - 1] = '>';
if (setenv ("TZ", value, 1) < 0)
{
printf ("setenv failed: %m\n");
return 1;
}
tzset ();
}
return errors > 0;
}