]> granicus.if.org Git - musl/commitdiff
fix strftime handling of time zone data
authorRich Felker <dalias@aerifal.cx>
Sat, 24 Aug 2013 16:59:02 +0000 (12:59 -0400)
committerRich Felker <dalias@aerifal.cx>
Sat, 24 Aug 2013 16:59:02 +0000 (12:59 -0400)
this may need further revision in the future, since POSIX is rather
unclear on the requirements, and is designed around the assumption of
POSIX TZ specifiers which are not sufficiently powerful to represent
real-world timezones (this is why zoneinfo support was added).

the basic issue is that strftime gets the string and numeric offset
for the timezone from the extra fields in struct tm, which are
initialized when calling localtime/gmtime/etc. however, a conforming
application might have created its own struct tm without initializing
these fields, in which case using __tm_zone (a pointer) could crash.
other zoneinfo-based implementations simply check for a null pointer,
but otherwise can still crash of the field contains junk.

simply ignoring __tm_zone and using tzname[] would "work" but would
give incorrect results in time zones with more complex rules. I feel
like this would lower the quality of implementation.

instead, simply validate __tm_zone: unless it points to one of the
zone name strings managed by the timezone system, assume it's invalid.

this commit also fixes several other minor bugs with formatting:
tm_isdst being negative is required to suppress printing of the zone
formats, and %z was using the wrong format specifiers since the type
of val was changed, resulting in bogus output.

src/time/__tz.c
src/time/gmtime_r.c
src/time/strftime.c
src/time/timegm.c

index 36b59802aa5865cbbf00972cad8e4bcb2070aa18..dfeac519641fb11ad19cdbf527f453bfd53d9c82 100644 (file)
@@ -15,11 +15,12 @@ weak_alias(__tzname, tzname);
 
 static char std_name[TZNAME_MAX+1];
 static char dst_name[TZNAME_MAX+1];
+const char __gmt[] = "GMT";
 
 static int dst_off;
 static int r0[5], r1[5];
 
-static const unsigned char *zi, *trans, *index, *types, *abbrevs;
+static const unsigned char *zi, *trans, *index, *types, *abbrevs, *abbrevs_end;
 static size_t map_size;
 
 static char old_tz_buf[32];
@@ -127,7 +128,7 @@ static void do_tzset()
                "/usr/share/zoneinfo/\0/share/zoneinfo/\0/etc/zoneinfo/\0";
 
        s = getenv("TZ");
-       if (!s || !*s) s = "GMT0";
+       if (!s || !*s) s = __gmt;
 
        if (old_tz && !strcmp(s, old_tz)) return;
 
@@ -184,6 +185,7 @@ static void do_tzset()
                index = trans + (zi_read32(trans-12) << scale);
                types = index + zi_read32(trans-12);
                abbrevs = types + 6*zi_read32(trans-8);
+               abbrevs_end = abbrevs + zi_read32(trans-4);
                if (zi[map_size-1] == '\n') {
                        for (s = (const char *)zi+map_size-2; *s!='\n'; s--);
                        s++;
@@ -192,7 +194,7 @@ static void do_tzset()
                }
        }
 
-       if (!s) s = "GMT0";
+       if (!s) s = __gmt;
        getname(std_name, &s);
        __tzname[0] = std_name;
        __timezone = getoff(&s);
@@ -387,3 +389,15 @@ void __tzset()
 }
 
 weak_alias(__tzset, tzset);
+
+const char *__tm_to_tzname(const struct tm *tm)
+{
+       const void *p = tm->__tm_zone;
+       LOCK(lock);
+       do_tzset();
+       if (p != __gmt && p != __tzname[0] && p != __tzname[1]
+           && (uintptr_t)p-(uintptr_t)abbrevs >= abbrevs_end - abbrevs)
+               p = "";
+       UNLOCK(lock);
+       return p;
+}
index fee01bd6ee866434df7c9efe13e21f1fd99572a2..8cbdadcb5d02164b845a7035dd68502ed794240b 100644 (file)
@@ -2,6 +2,8 @@
 #include <errno.h>
 #include "libc.h"
 
+extern const char __gmt[];
+
 struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm)
 {
        if (__secs_to_tm(*t, tm) < 0) {
@@ -10,7 +12,7 @@ struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm)
        }
        tm->tm_isdst = 0;
        tm->__tm_gmtoff = 0;
-       tm->__tm_zone = "GMT";
+       tm->__tm_zone = __gmt;
        return tm;
 }
 
index 24000f3bb6346267ad05916444e14db51f866061..48f6532085ee4b4c6e376edf3d6ad26de565543f 100644 (file)
@@ -43,6 +43,7 @@ static int week_num(const struct tm *tm)
        return val;
 }
 
+const char *__tm_to_tzname(const struct tm *);
 size_t __strftime_l(char *restrict, size_t, const char *restrict, const struct tm *restrict, locale_t);
 
 const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc)
@@ -166,11 +167,20 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *
                width = 4;
                goto number;
        case 'z':
-               val = -tm->__tm_gmtoff;
-               *l = snprintf(*s, sizeof *s, "%+.2d%.2d", val/3600, abs(val%3600)/60);
+               if (tm->tm_isdst < 0) {
+                       *l = 0;
+                       return "";
+               }
+               *l = snprintf(*s, sizeof *s, "%+.2d%.2d",
+                       (-tm->__tm_gmtoff)/3600,
+                       abs(tm->__tm_gmtoff%3600)/60);
                return *s;
        case 'Z':
-               fmt = tm->__tm_zone;
+               if (tm->tm_isdst < 0) {
+                       *l = 0;
+                       return "";
+               }
+               fmt = __tm_to_tzname(tm);
                goto string;
        case '%':
                *l = 1;
index e7a7939b3da2dfee380864357519be9a572d039e..b5dae8b648eca781ca9e726f90d5b6cc85b72d1a 100644 (file)
@@ -2,6 +2,8 @@
 #include "time_impl.h"
 #include <errno.h>
 
+extern const char __gmt[];
+
 time_t timegm(struct tm *tm)
 {
        struct tm new;
@@ -13,6 +15,6 @@ time_t timegm(struct tm *tm)
        *tm = new;
        tm->tm_isdst = 0;
        tm->__tm_gmtoff = 0;
-       tm->__tm_zone = "GMT";
+       tm->__tm_zone = __gmt;
        return t;
 }