diff --git a/NEWS b/NEWS index 0c80ffefb1b2..51cafdf08c7b 100644 --- a/NEWS +++ b/NEWS @@ -96,6 +96,8 @@ PHP NEWS file fails after it was already truncated. (Ilia Alshanetsky) - Standard: + . Fixed stream_set_timeout() overflowing the platform timeout range when + combining seconds and microseconds. (Ilia Alshanetsky) . Fixed chown() and lchown() failing to resolve user names in ZTS builds when getpwnam_r() needs a larger buffer. (Ilia Alshanetsky) . Fixed password_get_info() and password_needs_rehash() accepting malformed diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 391a72b5d9e0..fd2a0971ae79 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -1420,9 +1420,9 @@ PHP_FUNCTION(stream_set_timeout) { zval *socket; zend_long seconds, microseconds = 0; + int64_t normalized_seconds, additional_seconds; struct timeval t; php_stream *stream; - int argc = ZEND_NUM_ARGS(); ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_RESOURCE(socket) @@ -1433,24 +1433,25 @@ PHP_FUNCTION(stream_set_timeout) php_stream_from_zval(stream, socket); -#ifdef PHP_WIN32 - t.tv_sec = (long)seconds; - - if (argc == 3) { - t.tv_usec = (long)(microseconds % 1000000); - t.tv_sec +=(long)(microseconds / 1000000); - } else { - t.tv_usec = 0; + additional_seconds = microseconds / 1000000; + if ((additional_seconds > 0 && seconds > INT64_MAX - additional_seconds) + || (additional_seconds < 0 && seconds < INT64_MIN - additional_seconds)) { + zend_argument_value_error(2, "is out of range when combined with argument #3 ($microseconds)"); + RETURN_THROWS(); } -#else - t.tv_sec = seconds; - - if (argc == 3) { - t.tv_usec = microseconds % 1000000; - t.tv_sec += microseconds / 1000000; - } else { - t.tv_usec = 0; + normalized_seconds = (int64_t) seconds + additional_seconds; + const int64_t max_seconds = (UINT64_C(1) << (sizeof(t.tv_sec) * CHAR_BIT - 1)) - 1; + if (normalized_seconds > max_seconds || normalized_seconds < -max_seconds - 1) { + zend_argument_value_error(2, "is out of range when combined with argument #3 ($microseconds)"); + RETURN_THROWS(); } + +#ifdef PHP_WIN32 + t.tv_sec = (long)normalized_seconds; + t.tv_usec = (long)(microseconds % 1000000); +#else + t.tv_sec = normalized_seconds; + t.tv_usec = microseconds % 1000000; #endif if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)) { diff --git a/ext/standard/tests/streams/stream_set_timeout_overflow.phpt b/ext/standard/tests/streams/stream_set_timeout_overflow.phpt new file mode 100644 index 000000000000..4d2945f981c3 --- /dev/null +++ b/ext/standard/tests/streams/stream_set_timeout_overflow.phpt @@ -0,0 +1,64 @@ +--TEST-- +stream_set_timeout() rejects overflowing sums and preserves signed normalization +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; + } + var_dump(TimeoutStream::$timeout === []); +} + +echo "signed normalization and limits:\n"; +foreach ([[-1, 0, -1, 0], [1, -2_000_000, -1, 0], + [10, 2_345_678, 12, 345678], [10, -2_345_678, 8, -345678], + [$maximum - 1, 1_000_000, $maximum, 0], + [$minimum + 1, -1_000_000, $minimum, 0]] as [$seconds, $microseconds, $expectedSeconds, $expectedMicroseconds]) { + var_dump(stream_set_timeout($stream, $seconds, $microseconds) + && TimeoutStream::$timeout === [$expectedSeconds, $expectedMicroseconds]); +} +?> +--EXPECT-- +overflow: +ValueError: stream_set_timeout(): Argument #2 ($seconds) is out of range when combined with argument #3 ($microseconds) +bool(true) +ValueError: stream_set_timeout(): Argument #2 ($seconds) is out of range when combined with argument #3 ($microseconds) +bool(true) +signed normalization and limits: +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true)