Skip to content
Open
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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 18 additions & 17 deletions ext/standard/streamsfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)) {
Expand Down
64 changes: 64 additions & 0 deletions ext/standard/tests/streams/stream_set_timeout_overflow.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
stream_set_timeout() rejects overflowing sums and preserves signed normalization
--SKIPIF--
<?php
if (PHP_INT_SIZE != 8 && PHP_OS_FAMILY !== 'Windows') {
die("skip timeval may be wider than PHP integers on 32-bit systems");
}
?>
--FILE--
<?php
class TimeoutStream
{
public $context;
public static array $timeout = [];

public function stream_open($path, $mode, $options, &$openedPath): bool
{
return true;
}

public function stream_set_option($option, $seconds, $microseconds): bool
{
self::$timeout = [$seconds, $microseconds];
return true;
}
}

stream_wrapper_register('timeout', TimeoutStream::class);
$stream = fopen('timeout://test', 'r');
$maximum = PHP_OS_FAMILY === 'Windows' ? 2147483647 : PHP_INT_MAX;
$minimum = -$maximum - 1;

echo "overflow:\n";
foreach ([[$maximum, 1_000_000], [$minimum, -1_000_000]] as [$seconds, $microseconds]) {
try {
stream_set_timeout($stream, $seconds, $microseconds);
} catch (ValueError $e) {
echo $e::class, ': ', $e->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)
Loading