Skip to content

Commit cc1c5a0

Browse files
committed
Fix GH-#18051 (DateTimeZone->getTransitions can return first transition twice + integer overflow)
1 parent 4122daa commit cc1c5a0

File tree

4 files changed

+175
-17
lines changed

4 files changed

+175
-17
lines changed

ext/date/php_date.c

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4265,6 +4265,7 @@ PHP_FUNCTION(timezone_transitions_get)
42654265
uint64_t begin = 0;
42664266
bool found;
42674267
zend_long timestamp_begin = ZEND_LONG_MIN, timestamp_end = INT32_MAX;
4268+
zend_long last_transition_ts = ZEND_LONG_MIN;
42684269

42694270
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|ll", &object, date_ce_timezone, &timestamp_begin, &timestamp_end) == FAILURE) {
42704271
RETURN_THROWS();
@@ -4275,13 +4276,13 @@ PHP_FUNCTION(timezone_transitions_get)
42754276
RETURN_FALSE;
42764277
}
42774278

4278-
#define add_nominal() \
4279+
#define add_by_index(i,ts) \
42794280
array_init_size(&element, 5); \
4280-
add_assoc_long(&element, "ts", timestamp_begin); \
4281-
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, timestamp_begin, 0)); \
4282-
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[0].offset); \
4283-
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[0].isdst); \
4284-
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[0].abbr_idx]); \
4281+
add_assoc_long(&element, "ts", ts); \
4282+
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
4283+
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[i].offset); \
4284+
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[i].isdst); \
4285+
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[i].abbr_idx]); \
42854286
add_next_index_zval(return_value, &element);
42864287

42874288
#define add(i,ts) \
@@ -4293,14 +4294,13 @@ PHP_FUNCTION(timezone_transitions_get)
42934294
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].abbr_idx]); \
42944295
add_next_index_zval(return_value, &element);
42954296

4296-
#define add_by_index(i,ts) \
4297-
array_init_size(&element, 5); \
4297+
#define upd_prev(i,ts) \
4298+
element = *ZEND_HASH_ELEMENT(Z_ARRVAL_P(return_value), Z_ARRVAL_P(return_value)->nNumUsed-1); \
42984299
add_assoc_long(&element, "ts", ts); \
42994300
add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
4300-
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[i].offset); \
4301-
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[i].isdst); \
4302-
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[i].abbr_idx]); \
4303-
add_next_index_zval(return_value, &element);
4301+
add_assoc_long(&element, "offset", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].offset); \
4302+
add_assoc_bool(&element, "isdst", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].isdst); \
4303+
add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].abbr_idx]);
43044304

43054305
#define add_from_tto(to,ts) \
43064306
array_init_size(&element, 5); \
@@ -4311,6 +4311,7 @@ PHP_FUNCTION(timezone_transitions_get)
43114311
add_assoc_string(&element, "abbr", (to)->abbr); \
43124312
add_next_index_zval(return_value, &element);
43134313

4314+
#define add_nominal() add_by_index(0, timestamp_begin)
43144315
#define add_last() add(tzobj->tzi.tz->bit64.timecnt - 1, timestamp_begin)
43154316

43164317
array_init(return_value);
@@ -4327,8 +4328,10 @@ PHP_FUNCTION(timezone_transitions_get)
43274328
if (tzobj->tzi.tz->trans[begin] > timestamp_begin) {
43284329
if (begin > 0) {
43294330
add(begin - 1, timestamp_begin);
4331+
last_transition_ts = timestamp_begin;
43304332
} else {
43314333
add_nominal();
4334+
last_transition_ts = timestamp_begin;
43324335
}
43334336
found = 1;
43344337
break;
@@ -4344,24 +4347,32 @@ PHP_FUNCTION(timezone_transitions_get)
43444347
timelib_time_offset *tto = timelib_get_time_zone_info(timestamp_begin, tzobj->tzi.tz);
43454348
add_from_tto(tto, timestamp_begin);
43464349
timelib_time_offset_dtor(tto);
4350+
last_transition_ts = timestamp_begin;
43474351
} else {
43484352
add_last();
4353+
last_transition_ts = timestamp_begin;
43494354
}
43504355
} else {
43514356
add_nominal();
4357+
last_transition_ts = timestamp_begin;
43524358
}
43534359
} else {
43544360
for (uint64_t i = begin; i < tzobj->tzi.tz->bit64.timecnt; ++i) {
4355-
if (tzobj->tzi.tz->trans[i] < timestamp_end) {
4361+
if (tzobj->tzi.tz->trans[i] > timestamp_end) {
4362+
return;
4363+
}
4364+
4365+
if (tzobj->tzi.tz->trans[i] > timestamp_begin) {
43564366
add(i, tzobj->tzi.tz->trans[i]);
4367+
last_transition_ts = tzobj->tzi.tz->trans[i];
43574368
} else {
4358-
return;
4369+
upd_prev(i, timestamp_begin);
43594370
}
43604371
}
43614372
}
4373+
43624374
if (tzobj->tzi.tz->posix_info && tzobj->tzi.tz->posix_info->dst_end) {
43634375
timelib_sll start_y, end_y, dummy_m, dummy_d;
4364-
timelib_sll last_transition_ts = tzobj->tzi.tz->trans[tzobj->tzi.tz->bit64.timecnt - 1];
43654376

43664377
/* Find out year for last transition */
43674378
timelib_unixtime2date(last_transition_ts, &start_y, &dummy_m, &dummy_d);

ext/date/tests/bug80963.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ array(5) {
2727
["abbr"]=>
2828
string(3) "GMT"
2929
}
30-
int(237)
30+
int(23%d)
3131
array(5) {
3232
["ts"]=>
3333
int(2140668000)
@@ -40,7 +40,7 @@ array(5) {
4040
["abbr"]=>
4141
string(3) "EST"
4242
}
43-
int(144)
43+
int(14%d)
4444
array(5) {
4545
["ts"]=>
4646
int(2140045200)

ext/date/tests/gh18051-32bit.phpt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Bug GH-18051 (Interger overflow on 32bit)
3+
--SKIPIF--
4+
<?php if (PHP_INT_SIZE != 4) die("skip 32-bit only");
5+
--FILE--
6+
<?php
7+
8+
$tz = new DateTimeZone('America/New_York');
9+
var_dump(array_slice($tz->getTransitions(), 0, 3));
10+
11+
--EXPECT--
12+
array(3) {
13+
[0]=>
14+
array(5) {
15+
["ts"]=>
16+
int(-2147483648)
17+
["time"]=>
18+
string(25) "1901-12-13T20:45:52+00:00"
19+
["offset"]=>
20+
int(-18000)
21+
["isdst"]=>
22+
bool(false)
23+
["abbr"]=>
24+
string(3) "EST"
25+
}
26+
[1]=>
27+
array(5) {
28+
["ts"]=>
29+
int(-1633280400)
30+
["time"]=>
31+
string(25) "1918-03-31T07:00:00+00:00"
32+
["offset"]=>
33+
int(-14400)
34+
["isdst"]=>
35+
bool(true)
36+
["abbr"]=>
37+
string(3) "EDT"
38+
}
39+
[2]=>
40+
array(5) {
41+
["ts"]=>
42+
int(-1615140000)
43+
["time"]=>
44+
string(25) "1918-10-27T06:00:00+00:00"
45+
["offset"]=>
46+
int(-18000)
47+
["isdst"]=>
48+
bool(false)
49+
["abbr"]=>
50+
string(3) "EST"
51+
}
52+
}

ext/date/tests/gh18051.phpt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
--TEST--
2+
Bug GH-18051 (DateTimeZone->getTransitions can return first transition twice)
3+
--FILE--
4+
<?php
5+
6+
$tzBln = new DateTimeZone('Europe/Berlin');
7+
var_dump($tzBln->getTransitions(922582799, 941331599));
8+
var_dump($tzBln->getTransitions(922582800, 941331600));
9+
var_dump($tzBln->getTransitions(922582801, 941331601));
10+
11+
--EXPECT--
12+
array(2) {
13+
[0]=>
14+
array(5) {
15+
["ts"]=>
16+
int(922582799)
17+
["time"]=>
18+
string(25) "1999-03-28T00:59:59+00:00"
19+
["offset"]=>
20+
int(3600)
21+
["isdst"]=>
22+
bool(false)
23+
["abbr"]=>
24+
string(3) "CET"
25+
}
26+
[1]=>
27+
array(5) {
28+
["ts"]=>
29+
int(922582800)
30+
["time"]=>
31+
string(25) "1999-03-28T01:00:00+00:00"
32+
["offset"]=>
33+
int(7200)
34+
["isdst"]=>
35+
bool(true)
36+
["abbr"]=>
37+
string(4) "CEST"
38+
}
39+
}
40+
array(2) {
41+
[0]=>
42+
array(5) {
43+
["ts"]=>
44+
int(922582800)
45+
["time"]=>
46+
string(25) "1999-03-28T01:00:00+00:00"
47+
["offset"]=>
48+
int(7200)
49+
["isdst"]=>
50+
bool(true)
51+
["abbr"]=>
52+
string(4) "CEST"
53+
}
54+
[1]=>
55+
array(5) {
56+
["ts"]=>
57+
int(941331600)
58+
["time"]=>
59+
string(25) "1999-10-31T01:00:00+00:00"
60+
["offset"]=>
61+
int(3600)
62+
["isdst"]=>
63+
bool(false)
64+
["abbr"]=>
65+
string(3) "CET"
66+
}
67+
}
68+
array(2) {
69+
[0]=>
70+
array(5) {
71+
["ts"]=>
72+
int(922582801)
73+
["time"]=>
74+
string(25) "1999-03-28T01:00:01+00:00"
75+
["offset"]=>
76+
int(7200)
77+
["isdst"]=>
78+
bool(true)
79+
["abbr"]=>
80+
string(4) "CEST"
81+
}
82+
[1]=>
83+
array(5) {
84+
["ts"]=>
85+
int(941331600)
86+
["time"]=>
87+
string(25) "1999-10-31T01:00:00+00:00"
88+
["offset"]=>
89+
int(3600)
90+
["isdst"]=>
91+
bool(false)
92+
["abbr"]=>
93+
string(3) "CET"
94+
}
95+
}

0 commit comments

Comments
 (0)