]> git.pld-linux.org Git - packages/php.git/blame - php-systzdata.patch
- up to 5.6.33; fixes CVE-2018-5711, CVE-2018-5712
[packages/php.git] / php-systzdata.patch
CommitLineData
c0240cb1 1Add support for use of the system timezone database, rather
2than embedding a copy. Discussed upstream but was not desired.
3
4History:
a659fa18
ER
5r12: adapt for upstream changes for new zic
6r11: use canonical names to avoid more case sensitivity issues
7 round lat/long from zone.tab towards zero per builtin db
8r10: make timezone case insensitive
361fb152
ER
9r9: fix another compile error without --with-system-tzdata configured (Michael Heimpold)
10r8: fix compile error without --with-system-tzdata configured
c0240cb1 11r7: improve check for valid timezone id to exclude directories
a659fa18 12r6: fix fd leak in r5, fix country code/BC flag use in
c0240cb1 13 timezone_identifiers_list() using system db,
14 fix use of PECL timezonedb to override system db,
15r5: reverts addition of "System/Localtime" fake tzname.
16 updated for 5.3.0, parses zone.tab to pick up mapping between
17 timezone name, country code and long/lat coords
18r4: added "System/Localtime" tzname which uses /etc/localtime
19r3: fix a crash if /usr/share/zoneinfo doesn't exist (Raphael Geissert)
20r2: add filesystem trawl to set up name alias index
21r1: initial revision
22
9e9d8ae6
ER
23--- php-7.0.0RC1/ext/date/lib/parse_tz.c~ 2015-08-27 12:06:04.000000000 +0300
24+++ php-7.0.0RC1/ext/date/lib/parse_tz.c 2015-08-27 12:08:08.706661229 +0300
7588b36c 25@@ -20,6 +20,16 @@
c0240cb1 26
27 #include "timelib.h"
28
29+#ifdef HAVE_SYSTEM_TZDATA
30+#include <sys/mman.h>
31+#include <sys/stat.h>
32+#include <limits.h>
33+#include <fcntl.h>
34+#include <unistd.h>
35+
36+#include "php_scandir.h"
37+#endif
38+
39 #include <stdio.h>
40
41 #ifdef HAVE_LOCALE_H
9e9d8ae6 42@@ -43,7 +43,11 @@
c0240cb1 43 #endif
9e9d8ae6
ER
44
45 #define TIMELIB_SUPPORTS_V2DATA
c0240cb1 46+#ifndef HAVE_SYSTEM_TZDATA
47 #include "timezonedb.h"
48+#endif
49+
50+#include <ctype.h>
51
52 #if (defined(__APPLE__) || defined(__APPLE_CC__)) && (defined(__BIG_ENDIAN__) || defined(__LITTLE_ENDIAN__))
53 # if defined(__LITTLE_ENDIAN__)
a659fa18 54@@ -53,6 +68,10 @@ static int read_preamble(const unsigned
c0240cb1 55 {
a659fa18
ER
56 uint32_t version;
57
58+ if (memcmp(*tzf, "TZif", 4) == 0) {
59+ *tzf += 20;
60+ return 0;
61+ }
62 /* read ID */
63 version = (*tzf)[3] - '0';
64 *tzf += 4;
65@@ -296,7 +315,418 @@ void timelib_dump_tzinfo(timelib_tzinfo
c0240cb1 66 }
67 }
68
69-static int seek_to_tz_position(const unsigned char **tzf, char *timezone, const timelib_tzdb *tzdb)
70+#ifdef HAVE_SYSTEM_TZDATA
71+
72+#ifdef HAVE_SYSTEM_TZDATA_PREFIX
73+#define ZONEINFO_PREFIX HAVE_SYSTEM_TZDATA_PREFIX
74+#else
75+#define ZONEINFO_PREFIX "/usr/share/zoneinfo"
76+#endif
77+
78+/* System timezone database pointer. */
7588b36c 79+static const timelib_tzdb *timezonedb_system;
c0240cb1 80+
81+/* Hash table entry for the cache of the zone.tab mapping table. */
82+struct location_info {
83+ char code[2];
84+ double latitude, longitude;
85+ char name[64];
86+ char *comment;
87+ struct location_info *next;
88+};
89+
90+/* Cache of zone.tab. */
91+static struct location_info **system_location_table;
92+
93+/* Size of the zone.tab hash table; a random-ish prime big enough to
94+ * prevent too many collisions. */
95+#define LOCINFO_HASH_SIZE (1021)
96+
7588b36c 97+/* Compute a case insensitive hash of str */
c0240cb1 98+static uint32_t tz_hash(const char *str)
99+{
100+ const unsigned char *p = (const unsigned char *)str;
101+ uint32_t hash = 5381;
102+ int c;
103+
7588b36c 104+ while ((c = tolower(*p++)) != '\0') {
c0240cb1 105+ hash = (hash << 5) ^ hash ^ c;
106+ }
107+
108+ return hash % LOCINFO_HASH_SIZE;
109+}
110+
111+/* Parse an ISO-6709 date as used in zone.tab. Returns end of the
112+ * parsed string on success, or NULL on parse error. On success,
113+ * writes the parsed number to *result. */
114+static char *parse_iso6709(char *p, double *result)
115+{
116+ double v, sign;
117+ char *pend;
118+ size_t len;
119+
120+ if (*p == '+')
121+ sign = 1.0;
122+ else if (*p == '-')
123+ sign = -1.0;
124+ else
125+ return NULL;
126+
127+ p++;
128+ for (pend = p; *pend >= '0' && *pend <= '9'; pend++)
129+ ;;
130+
131+ /* Annoying encoding used by zone.tab has no decimal point, so use
132+ * the length to determine the format:
133+ *
134+ * 4 = DDMM
135+ * 5 = DDDMM
136+ * 6 = DDMMSS
137+ * 7 = DDDMMSS
138+ */
139+ len = pend - p;
140+ if (len < 4 || len > 7) {
141+ return NULL;
142+ }
143+
144+ /* p => [D]DD */
145+ v = (p[0] - '0') * 10.0 + (p[1] - '0');
146+ p += 2;
147+ if (len == 5 || len == 7)
148+ v = v * 10.0 + (*p++ - '0');
149+ /* p => MM[SS] */
150+ v += (10.0 * (p[0] - '0')
151+ + p[1] - '0') / 60.0;
152+ p += 2;
153+ /* p => [SS] */
154+ if (len > 5) {
155+ v += (10.0 * (p[0] - '0')
156+ + p[1] - '0') / 3600.0;
157+ p += 2;
158+ }
159+
160+ /* Round to five decimal place, not because it's a good idea,
161+ * but, because the builtin data uses rounded data, so, match
162+ * that. */
a659fa18 163+ *result = trunc(v * sign * 100000.0) / 100000.0;
c0240cb1 164+
165+ return p;
166+}
167+
168+/* This function parses the zone.tab file to build up the mapping of
169+ * timezone to country code and geographic location, and returns a
170+ * hash table. The hash table is indexed by the function:
171+ *
172+ * tz_hash(timezone-name)
173+ */
174+static struct location_info **create_location_table(void)
175+{
176+ struct location_info **li, *i;
177+ char zone_tab[PATH_MAX];
178+ char line[512];
179+ FILE *fp;
180+
181+ strncpy(zone_tab, ZONEINFO_PREFIX "/zone.tab", sizeof zone_tab);
182+
183+ fp = fopen(zone_tab, "r");
184+ if (!fp) {
185+ return NULL;
186+ }
187+
188+ li = calloc(LOCINFO_HASH_SIZE, sizeof *li);
189+
190+ while (fgets(line, sizeof line, fp)) {
191+ char *p = line, *code, *name, *comment;
192+ uint32_t hash;
193+ double latitude, longitude;
194+
195+ while (isspace(*p))
196+ p++;
197+
198+ if (*p == '#' || *p == '\0' || *p == '\n')
199+ continue;
200+
201+ if (!isalpha(p[0]) || !isalpha(p[1]) || p[2] != '\t')
202+ continue;
203+
204+ /* code => AA */
205+ code = p;
206+ p[2] = 0;
207+ p += 3;
208+
209+ /* coords => [+-][D]DDMM[SS][+-][D]DDMM[SS] */
210+ p = parse_iso6709(p, &latitude);
211+ if (!p) {
212+ continue;
213+ }
214+ p = parse_iso6709(p, &longitude);
215+ if (!p) {
216+ continue;
217+ }
218+
219+ if (!p || *p != '\t') {
220+ continue;
221+ }
222+
223+ /* name = string */
224+ name = ++p;
225+ while (*p != '\t' && *p && *p != '\n')
226+ p++;
227+
228+ *p++ = '\0';
229+
230+ /* comment = string */
231+ comment = p;
232+ while (*p != '\t' && *p && *p != '\n')
233+ p++;
234+
235+ if (*p == '\n' || *p == '\t')
236+ *p = '\0';
237+
238+ hash = tz_hash(name);
239+ i = malloc(sizeof *i);
240+ memcpy(i->code, code, 2);
241+ strncpy(i->name, name, sizeof i->name);
242+ i->comment = strdup(comment);
243+ i->longitude = longitude;
244+ i->latitude = latitude;
245+ i->next = li[hash];
246+ li[hash] = i;
247+ /* printf("%s [%u, %f, %f]\n", name, hash, latitude, longitude); */
248+ }
249+
250+ fclose(fp);
251+
252+ return li;
253+}
254+
255+/* Return location info from hash table, using given timezone name.
256+ * Returns NULL if the name could not be found. */
257+const struct location_info *find_zone_info(struct location_info **li,
258+ const char *name)
259+{
260+ uint32_t hash = tz_hash(name);
261+ const struct location_info *l;
262+
263+ if (!li) {
264+ return NULL;
265+ }
266+
267+ for (l = li[hash]; l; l = l->next) {
268+ if (strcasecmp(l->name, name) == 0)
269+ return l;
270+ }
271+
272+ return NULL;
273+}
274+
275+/* Filter out some non-tzdata files and the posix/right databases, if
276+ * present. */
277+static int index_filter(const struct dirent *ent)
278+{
279+ return strcmp(ent->d_name, ".") != 0
280+ && strcmp(ent->d_name, "..") != 0
281+ && strcmp(ent->d_name, "posix") != 0
282+ && strcmp(ent->d_name, "posixrules") != 0
283+ && strcmp(ent->d_name, "right") != 0
284+ && strstr(ent->d_name, ".tab") == NULL;
285+}
286+
287+static int sysdbcmp(const void *first, const void *second)
288+{
289+ const timelib_tzdb_index_entry *alpha = first, *beta = second;
290+
a659fa18 291+ return strcasecmp(alpha->id, beta->id);
c0240cb1 292+}
293+
294+
295+/* Create the zone identifier index by trawling the filesystem. */
296+static void create_zone_index(timelib_tzdb *db)
297+{
298+ size_t dirstack_size, dirstack_top;
299+ size_t index_size, index_next;
300+ timelib_tzdb_index_entry *db_index;
301+ char **dirstack;
302+
303+ /* LIFO stack to hold directory entries to scan; each slot is a
304+ * directory name relative to the zoneinfo prefix. */
305+ dirstack_size = 32;
306+ dirstack = malloc(dirstack_size * sizeof *dirstack);
307+ dirstack_top = 1;
308+ dirstack[0] = strdup("");
309+
310+ /* Index array. */
311+ index_size = 64;
312+ db_index = malloc(index_size * sizeof *db_index);
313+ index_next = 0;
314+
315+ do {
316+ struct dirent **ents;
317+ char name[PATH_MAX], *top;
318+ int count;
319+
320+ /* Pop the top stack entry, and iterate through its contents. */
321+ top = dirstack[--dirstack_top];
322+ snprintf(name, sizeof name, ZONEINFO_PREFIX "/%s", top);
323+
324+ count = php_scandir(name, &ents, index_filter, php_alphasort);
325+
326+ while (count > 0) {
327+ struct stat st;
328+ const char *leaf = ents[count - 1]->d_name;
329+
330+ snprintf(name, sizeof name, ZONEINFO_PREFIX "/%s/%s",
331+ top, leaf);
332+
333+ if (strlen(name) && stat(name, &st) == 0) {
334+ /* Name, relative to the zoneinfo prefix. */
335+ const char *root = top;
336+
337+ if (root[0] == '/') root++;
338+
339+ snprintf(name, sizeof name, "%s%s%s", root,
340+ *root ? "/": "", leaf);
341+
342+ if (S_ISDIR(st.st_mode)) {
343+ if (dirstack_top == dirstack_size) {
344+ dirstack_size *= 2;
345+ dirstack = realloc(dirstack,
346+ dirstack_size * sizeof *dirstack);
347+ }
348+ dirstack[dirstack_top++] = strdup(name);
349+ }
350+ else {
351+ if (index_next == index_size) {
352+ index_size *= 2;
353+ db_index = realloc(db_index,
354+ index_size * sizeof *db_index);
355+ }
356+
357+ db_index[index_next++].id = strdup(name);
358+ }
359+ }
360+
361+ free(ents[--count]);
362+ }
363+
364+ if (count != -1) free(ents);
365+ free(top);
366+ } while (dirstack_top);
367+
368+ qsort(db_index, index_next, sizeof *db_index, sysdbcmp);
369+
370+ db->index = db_index;
371+ db->index_size = index_next;
372+
373+ free(dirstack);
374+}
375+
376+#define FAKE_HEADER "1234\0??\1??"
377+#define FAKE_UTC_POS (7 - 4)
378+
379+/* Create a fake data segment for database 'sysdb'. */
380+static void fake_data_segment(timelib_tzdb *sysdb,
381+ struct location_info **info)
382+{
383+ size_t n;
384+ char *data, *p;
385+
386+ data = malloc(3 * sysdb->index_size + 7);
387+
388+ p = mempcpy(data, FAKE_HEADER, sizeof(FAKE_HEADER) - 1);
389+
390+ for (n = 0; n < sysdb->index_size; n++) {
391+ const struct location_info *li;
392+ timelib_tzdb_index_entry *ent;
393+
394+ ent = (timelib_tzdb_index_entry *)&sysdb->index[n];
395+
396+ /* Lookup the timezone name in the hash table. */
397+ if (strcmp(ent->id, "UTC") == 0) {
398+ ent->pos = FAKE_UTC_POS;
399+ continue;
400+ }
401+
402+ li = find_zone_info(info, ent->id);
403+ if (li) {
404+ /* If found, append the BC byte and the
405+ * country code; set the position for this
406+ * section of timezone data. */
407+ ent->pos = (p - data) - 4;
408+ *p++ = '\1';
409+ *p++ = li->code[0];
410+ *p++ = li->code[1];
411+ }
412+ else {
413+ /* If not found, the timezone data can
414+ * point at the header. */
415+ ent->pos = 0;
416+ }
417+ }
418+
419+ sysdb->data = (unsigned char *)data;
420+}
421+
422+/* Returns true if the passed-in stat structure describes a
423+ * probably-valid timezone file. */
424+static int is_valid_tzfile(const struct stat *st)
425+{
426+ return S_ISREG(st->st_mode) && st->st_size > 20;
427+}
428+
a659fa18
ER
429+/* To allow timezone names to be used case-insensitively, find the
430+ * canonical name for this timezone, if possible. */
431+static const char *canonical_tzname(const char *timezone)
432+{
433+ if (timezonedb_system) {
434+ timelib_tzdb_index_entry *ent, lookup;
435+
436+ lookup.id = (char *)timezone;
437+
438+ ent = bsearch(&lookup, timezonedb_system->index,
439+ timezonedb_system->index_size, sizeof lookup,
440+ sysdbcmp);
441+ if (ent) {
442+ return ent->id;
443+ }
444+ }
445+
446+ return timezone;
447+}
448+
c0240cb1 449+/* Return the mmap()ed tzfile if found, else NULL. On success, the
450+ * length of the mapped data is placed in *length. */
451+static char *map_tzfile(const char *timezone, size_t *length)
452+{
453+ char fname[PATH_MAX];
454+ struct stat st;
455+ char *p;
456+ int fd;
457+
458+ if (timezone[0] == '\0' || strstr(timezone, "..") != NULL) {
459+ return NULL;
460+ }
461+
a659fa18 462+ snprintf(fname, sizeof fname, ZONEINFO_PREFIX "/%s", canonical_tzname(timezone));
c0240cb1 463+
464+ fd = open(fname, O_RDONLY);
465+ if (fd == -1) {
466+ return NULL;
467+ } else if (fstat(fd, &st) != 0 || !is_valid_tzfile(&st)) {
468+ close(fd);
469+ return NULL;
470+ }
471+
472+ *length = st.st_size;
473+ p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
474+ close(fd);
475+
476+ return p != MAP_FAILED ? p : NULL;
477+}
478+
479+#endif
480+
481+static int inmem_seek_to_tz_position(const unsigned char **tzf, char *timezone, const timelib_tzdb *tzdb)
482 {
483 int left = 0, right = tzdb->index_size - 1;
484 #ifdef HAVE_SETLOCALE
a659fa18 485@@ -335,21 +765,87 @@ static int seek_to_tz_position(const uns
c0240cb1 486 return 0;
487 }
488
a659fa18 489+static int seek_to_tz_position(const unsigned char **tzf, char *timezone,
c0240cb1 490+ char **map, size_t *maplen,
491+ const timelib_tzdb *tzdb)
492+{
361fb152 493+#ifdef HAVE_SYSTEM_TZDATA
c0240cb1 494+ if (tzdb == timezonedb_system) {
495+ char *orig;
496+
497+ orig = map_tzfile(timezone, maplen);
498+ if (orig == NULL) {
499+ return 0;
500+ }
a659fa18 501+
c0240cb1 502+ (*tzf) = (unsigned char *)orig ;
503+ *map = orig;
a659fa18 504+ return 1;
c0240cb1 505+ }
a659fa18 506+ else
361fb152 507+#endif
a659fa18 508+ {
c0240cb1 509+ return inmem_seek_to_tz_position(tzf, timezone, tzdb);
510+ }
511+}
512+
513 const timelib_tzdb *timelib_builtin_db(void)
514 {
515+#ifdef HAVE_SYSTEM_TZDATA
516+ if (timezonedb_system == NULL) {
517+ timelib_tzdb *tmp = malloc(sizeof *tmp);
518+
519+ tmp->version = "0.system";
520+ tmp->data = NULL;
521+ create_zone_index(tmp);
522+ system_location_table = create_location_table();
a659fa18 523+ fake_data_segment(tmp, system_location_table);
c0240cb1 524+ timezonedb_system = tmp;
525+ }
526+
c0240cb1 527+ return timezonedb_system;
528+#else
529 return &timezonedb_builtin;
530+#endif
531 }
532
533 const timelib_tzdb_index_entry *timelib_timezone_builtin_identifiers_list(int *count)
534 {
535+#ifdef HAVE_SYSTEM_TZDATA
536+ *count = timezonedb_system->index_size;
537+ return timezonedb_system->index;
538+#else
539 *count = sizeof(timezonedb_idx_builtin) / sizeof(*timezonedb_idx_builtin);
540 return timezonedb_idx_builtin;
541+#endif
542 }
543
544 int timelib_timezone_id_is_valid(char *timezone, const timelib_tzdb *tzdb)
545 {
546 const unsigned char *tzf;
547- return (seek_to_tz_position(&tzf, timezone, tzdb));
548+
549+#ifdef HAVE_SYSTEM_TZDATA
a659fa18
ER
550+ if (tzdb == timezonedb_system) {
551+ char fname[PATH_MAX];
552+ struct stat st;
553+
554+ if (timezone[0] == '\0' || strstr(timezone, "..") != NULL) {
555+ return 0;
556+ }
c0240cb1 557+
a659fa18
ER
558+ if (system_location_table) {
559+ if (find_zone_info(system_location_table, timezone) != NULL) {
560+ /* found in cache */
561+ return 1;
562+ }
563+ }
564+
565+ snprintf(fname, sizeof fname, ZONEINFO_PREFIX "/%s", canonical_tzname(timezone));
566+
567+ return stat(fname, &st) == 0 && is_valid_tzfile(&st);
568+ }
569+#endif
c0240cb1 570+ return (inmem_seek_to_tz_position(&tzf, timezone, tzdb));
571 }
572
a659fa18
ER
573 static void skip_64bit_preamble(const unsigned char **tzf, timelib_tzinfo *tz)
574@@ -374,24 +870,54 @@ static void read_64bit_header(const unsi
c0240cb1 575 timelib_tzinfo *timelib_parse_tzfile(char *timezone, const timelib_tzdb *tzdb)
576 {
577 const unsigned char *tzf;
578+ char *memmap = NULL;
579+ size_t maplen;
580 timelib_tzinfo *tmp;
a659fa18 581 int version;
c0240cb1 582
583- if (seek_to_tz_position(&tzf, timezone, tzdb)) {
584+ if (seek_to_tz_position(&tzf, timezone, &memmap, &maplen, tzdb)) {
585 tmp = timelib_tzinfo_ctor(timezone);
586
a659fa18 587 version = read_preamble(&tzf, tmp);
c0240cb1 588 read_header(&tzf, tmp);
589 read_transistions(&tzf, tmp);
590 read_types(&tzf, tmp);
a659fa18
ER
591- if (version == 2) {
592- skip_64bit_preamble(&tzf, tmp);
593- read_64bit_header(&tzf, tmp);
594- skip_64bit_transistions(&tzf, tmp);
595- skip_64bit_types(&tzf, tmp);
596- skip_posix_string(&tzf, tmp);
597- }
c0240cb1 598- read_location(&tzf, tmp);
599+
600+#ifdef HAVE_SYSTEM_TZDATA
601+ if (memmap) {
602+ const struct location_info *li;
603+
604+ /* TZif-style - grok the location info from the system database,
605+ * if possible. */
606+
607+ if ((li = find_zone_info(system_location_table, timezone)) != NULL) {
608+ tmp->location.comments = strdup(li->comment);
609+ strncpy(tmp->location.country_code, li->code, 2);
610+ tmp->location.longitude = li->longitude;
611+ tmp->location.latitude = li->latitude;
612+ tmp->bc = 1;
613+ }
614+ else {
615+ strcpy(tmp->location.country_code, "??");
616+ tmp->bc = 0;
617+ tmp->location.comments = strdup("");
618+ }
619+
620+ /* Now done with the mmap segment - discard it. */
621+ munmap(memmap, maplen);
361fb152 622+ } else
c0240cb1 623+#endif
361fb152 624+ {
a659fa18
ER
625+ if (version == 2) {
626+ skip_64bit_preamble(&tzf, tmp);
627+ read_64bit_header(&tzf, tmp);
628+ skip_64bit_transistions(&tzf, tmp);
629+ skip_64bit_types(&tzf, tmp);
630+ skip_posix_string(&tzf, tmp);
631+ }
c0240cb1 632+ /* PHP-style - use the embedded info. */
633+ read_location(&tzf, tmp);
a659fa18 634+ }
c0240cb1 635 } else {
636 tmp = NULL;
637 }
a659fa18
ER
638diff -up php-5.6.9RC1/ext/date/lib/timelib.m4.systzdata php-5.6.9RC1/ext/date/lib/timelib.m4
639--- php-5.6.9RC1/ext/date/lib/timelib.m4.systzdata 2015-04-30 00:00:18.000000000 +0200
640+++ php-5.6.9RC1/ext/date/lib/timelib.m4 2015-04-30 06:32:08.549500385 +0200
c0240cb1 641@@ -78,3 +78,17 @@ stdlib.h
642
643 dnl Check for strtoll, atoll
644 AC_CHECK_FUNCS(strtoll atoll strftime)
645+
646+PHP_ARG_WITH(system-tzdata, for use of system timezone data,
647+[ --with-system-tzdata[=DIR] to specify use of system timezone data],
648+no, no)
649+
650+if test "$PHP_SYSTEM_TZDATA" != "no"; then
651+ AC_DEFINE(HAVE_SYSTEM_TZDATA, 1, [Define if system timezone data is used])
652+
653+ if test "$PHP_SYSTEM_TZDATA" != "yes"; then
654+ AC_DEFINE_UNQUOTED(HAVE_SYSTEM_TZDATA_PREFIX, "$PHP_SYSTEM_TZDATA",
655+ [Define for location of system timezone data])
656+ fi
657+fi
658+
This page took 0.706022 seconds and 4 git commands to generate.