Skip to content

Fixed int32 underflow on DateTime::createFromTimestamp #12775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions ext/date/php_date.c
Original file line number Diff line number Diff line change
Expand Up @@ -2525,16 +2525,14 @@ PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts)
zend_long sec;
int usec;

if (UNEXPECTED(isnan(sec_dval)
|| sec_dval >= (double)TIMELIB_LONG_MAX
|| sec_dval < (double)TIMELIB_LONG_MIN
)) {
zend_throw_error(
if (UNEXPECTED(isnan(sec_dval) || !PHP_DATE_DOUBLE_FITS_LONG(sec_dval))) {
zend_argument_error(
date_ce_date_range_error,
"Seconds must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ", %g given",
1,
"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
TIMELIB_LONG_MIN,
TIMELIB_LONG_MAX,
sec_dval
ts
);
return false;
}
Expand All @@ -2543,6 +2541,18 @@ PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts)
usec = (int)(fmod(ts, 1) * 1000000);

if (UNEXPECTED(usec < 0)) {
if (UNEXPECTED(sec == TIMELIB_LONG_MIN)) {
zend_argument_error(
date_ce_date_range_error,
1,
"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
TIMELIB_LONG_MIN,
TIMELIB_LONG_MAX,
ts
);
return false;
}

sec = sec - 1;
usec = 1000000 + usec;
}
Expand Down
17 changes: 17 additions & 0 deletions ext/date/php_date.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@
#include "lib/timelib.h"
#include "Zend/zend_hash.h"

/* Same as SIZEOF_ZEND_LONG but using TIMELIB_LONG_MAX/MIN */
#if TIMELIB_LONG_MAX == INT32_MAX
# define PHP_DATE_SIZEOF_LONG 4
#elif TIMELIB_LONG_MAX == INT64_MAX
# define PHP_DATE_SIZEOF_LONG 8
#else
# error "Unknown TIMELIB LONG SIZE"
#endif

/* Same as ZEND_DOUBLE_FITS_LONG but using TIMELIB_LONG_MAX/MIN */
#if PHP_DATE_SIZEOF_LONG == 4
# define PHP_DATE_DOUBLE_FITS_LONG(d) (!((d) > (double)TIMELIB_LONG_MAX || (d) < (double)TIMELIB_LONG_MIN))
#elif PHP_DATE_SIZEOF_LONG == 8
/* >= as (double)TIMELIB_LONG_MAX is outside signed range */
# define PHP_DATE_DOUBLE_FITS_LONG(d) (!((d) >= (double)TIMELIB_LONG_MAX || (d) < (double)TIMELIB_LONG_MIN))
#endif

#include "php_version.h"
#define PHP_DATE_VERSION PHP_VERSION

Expand Down
79 changes: 10 additions & 69 deletions ext/date/tests/createFromTimestamp.phpt
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
--TEST--
Tests for DateTime[Immutable]::createFromTimestamp
--SKIPIF--
<?php
if (PHP_INT_SIZE === 4) die('xfail fails on 32-bit');
?>
--INI--
date.timezone=Europe/London
--FILE--
Expand All @@ -12,10 +8,6 @@ date.timezone=Europe/London
class MyDateTime extends DateTime {};
class MyDateTimeImmutable extends DateTimeImmutable {};

define('MAX_32BIT', 2147483647);
// -2147483648 may not be expressed in a literal due to parsing peculiarities.
define('MIN_32BIT', -2147483647 - 1);

$timestamps = array(
1696883232,
-1696883232,
Expand All @@ -26,9 +18,6 @@ $timestamps = array(
0,
0.0,
-0.0,
MAX_32BIT,
MIN_32BIT,
MIN_32BIT - 0.5,
PHP_INT_MAX + 1024.0,
PHP_INT_MIN - 1025.0,
NAN,
Expand Down Expand Up @@ -212,64 +201,16 @@ DateTimeImmutable::createFromTimestamp(-0.0): object(DateTimeImmutable)#%d (3) {
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(2147483647): object(DateTime)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(2147483647): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(-2147483648): object(DateTime)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(-2147483648): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(-2147483648.5): object(DateTime)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:51.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(-2147483648.5): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:51.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given
DateTimeImmutable::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given
DateTime::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given
DateTimeImmutable::createFromTimestamp(%f): DateRangeError: Seconds must be a finite number between %i and %i, %f given
DateTime::createFromTimestamp(NAN): DateRangeError: Seconds must be a finite number between %i and %i, NAN given
DateTimeImmutable::createFromTimestamp(NAN): DateRangeError: Seconds must be a finite number between %i and %i, NAN given
DateTime::createFromTimestamp(INF): DateRangeError: Seconds must be a finite number between %i and %i, INF given
DateTimeImmutable::createFromTimestamp(INF): DateRangeError: Seconds must be a finite number between %i and %i, INF given
DateTime::createFromTimestamp(-INF): DateRangeError: Seconds must be a finite number between %i and %i, -INF given
DateTimeImmutable::createFromTimestamp(-INF): DateRangeError: Seconds must be a finite number between %i and %i, -INF given
DateTime::createFromTimestamp(%f): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, %f given
DateTimeImmutable::createFromTimestamp(%f): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, %f given
DateTime::createFromTimestamp(%f): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, %f given
DateTimeImmutable::createFromTimestamp(%f): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, %f given
DateTime::createFromTimestamp(NAN): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, NAN given
DateTimeImmutable::createFromTimestamp(NAN): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, NAN given
DateTime::createFromTimestamp(INF): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, INF given
DateTimeImmutable::createFromTimestamp(INF): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, INF given
DateTime::createFromTimestamp(-INF): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, -INF given
DateTimeImmutable::createFromTimestamp(-INF): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %i and %i.999999, -INF given
MyDateTime::createFromTimestamp(0): object(MyDateTime)#%d (3) {
["date"]=>
string(26) "1970-01-01 00:00:00.000000"
Expand Down
126 changes: 126 additions & 0 deletions ext/date/tests/createFromTimestamp_32bit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
--TEST--
Tests for DateTime[Immutable]::createFromTimestamp 32bit variant
--SKIPIF--
<?php
if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only");
?>
--INI--
date.timezone=Europe/London
--FILE--
<?php

$timestamps = array(
PHP_INT_MAX,
PHP_INT_MIN,
PHP_INT_MAX + 0.5,
PHP_INT_MIN + 0.5,
PHP_INT_MAX - 0.5,
PHP_INT_MIN - 0.5,
PHP_INT_MAX + 1024.0,
PHP_INT_MIN - 1025.0,
);

foreach ($timestamps as $ts) {
echo 'DateTime::createFromTimestamp(' . var_export($ts, true) . '): ';
try {
var_dump(DateTime::createFromTimestamp($ts));
} catch (Throwable $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}

echo 'DateTimeImmutable::createFromTimestamp(' . var_export($ts, true) . '): ';
try {
var_dump(DateTimeImmutable::createFromTimestamp($ts));
} catch (Throwable $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}
}

?>
--EXPECTF--
DateTime::createFromTimestamp(2147483647): object(DateTime)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(2147483647): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(-2147483647-1): object(DateTime)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(-2147483647-1): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(2147483647.5): object(DateTime)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(2147483647.5): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:07.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(-2147483647.5): object(DateTime)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(-2147483647.5): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "1901-12-13 20:45:52.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(2147483646.5): object(DateTime)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:06.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTimeImmutable::createFromTimestamp(2147483646.5): object(DateTimeImmutable)#%d (3) {
["date"]=>
string(26) "2038-01-19 03:14:06.500000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
DateTime::createFromTimestamp(-2147483648.5): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, -2.14748e+9 given
DateTimeImmutable::createFromTimestamp(-2147483648.5): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, -2.14748e+9 given
DateTime::createFromTimestamp(2147484671.0): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, 2.14748e+9 given
DateTimeImmutable::createFromTimestamp(2147484671.0): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, 2.14748e+9 given
DateTime::createFromTimestamp(-2147484673.0): DateRangeError: DateTime::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, -2.14748e+9 given
DateTimeImmutable::createFromTimestamp(-2147484673.0): DateRangeError: DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between -2147483648 and 2147483647.999999, -2.14748e+9 given