Commit 7adcf5a580c for php
commit 7adcf5a580c3b6f2175dd2de053c1d440792ec26
Author: lazerg <lazerg2@gmail.com>
Date: Mon Oct 26 20:38:02 2026 +0500
ext/sockets: Handle declared object properties in socket_set_option()
Read option values through a retained property table and resolve indirect
slots before conversion.
Fixes GH-24748
Closes GH-24749
diff --git a/NEWS b/NEWS
index 4c27156901d..464c723e6aa 100644
--- a/NEWS
+++ b/NEWS
@@ -175,6 +175,10 @@ PHP NEWS
. Fixed bug GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c).
(Lazizbek Ergashev)
+- Sockets:
+ . Fixed bug GH-24748 (socket_set_option() crashes with declared object
+ properties). (Lazizbek Ergashev)
+
- Standard:
. Fixed bug GH-24308 (Request shutdown calls closelog() even when syslog
was not opened by PHP). (lazerg)
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index 5e70df60726..c1e7ead5546 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -1881,7 +1881,7 @@ PHP_FUNCTION(socket_set_option)
#endif
zend_long level, optname;
void *opt_ptr;
- HashTable *opt_ht;
+ HashTable *opt_ht, *opt_props = NULL;
zval *l_onoff, *l_linger;
zval *sec, *usec;
@@ -1955,23 +1955,27 @@ PHP_FUNCTION(socket_set_option)
zend_argument_type_error(4, "must be of type array when argument #3 ($option) is SO_LINGER, %s given", zend_zval_value_name(arg4));
RETURN_THROWS();
} else {
- opt_ht = Z_OBJPROP_P(arg4);
+ opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST);
+ opt_ht = opt_props ? opt_props : (HashTable *) &zend_empty_array;
}
} else {
opt_ht = Z_ARRVAL_P(arg4);
}
- if ((l_onoff = zend_hash_str_find(opt_ht, l_onoff_key, sizeof(l_onoff_key) - 1)) == NULL) {
+ if ((l_onoff = zend_hash_str_find_ind(opt_ht, l_onoff_key, sizeof(l_onoff_key) - 1)) == NULL) {
+ zend_release_properties(opt_props);
zend_argument_value_error(4, "must have key \"%s\"", l_onoff_key);
RETURN_THROWS();
}
- if ((l_linger = zend_hash_str_find(opt_ht, l_linger_key, sizeof(l_linger_key) - 1)) == NULL) {
+ if ((l_linger = zend_hash_str_find_ind(opt_ht, l_linger_key, sizeof(l_linger_key) - 1)) == NULL) {
+ zend_release_properties(opt_props);
zend_argument_value_error(4, "must have key \"%s\"", l_linger_key);
RETURN_THROWS();
}
zend_long val_lonoff = zval_get_long(l_onoff);
zend_long val_linger = zval_get_long(l_linger);
+ zend_release_properties(opt_props);
if (val_lonoff < 0 || val_lonoff > USHRT_MAX) {
zend_argument_value_error(4, "\"%s\" must be between 0 and %u", l_onoff_key, USHRT_MAX);
@@ -1979,7 +1983,7 @@ PHP_FUNCTION(socket_set_option)
}
if (val_linger < 0 || val_linger > USHRT_MAX) {
- zend_argument_value_error(4, "\"%s\" must be between 0 and %d", l_linger, USHRT_MAX);
+ zend_argument_value_error(4, "\"%s\" must be between 0 and %d", l_linger_key, USHRT_MAX);
RETURN_THROWS();
}
@@ -2003,23 +2007,27 @@ PHP_FUNCTION(socket_set_option)
zend_zval_value_name(arg4));
RETURN_THROWS();
} else {
- opt_ht = Z_OBJPROP_P(arg4);
+ opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST);
+ opt_ht = opt_props ? opt_props : (HashTable *) &zend_empty_array;
}
} else {
opt_ht = Z_ARRVAL_P(arg4);
}
- if ((sec = zend_hash_str_find(opt_ht, sec_key, sizeof(sec_key) - 1)) == NULL) {
+ if ((sec = zend_hash_str_find_ind(opt_ht, sec_key, sizeof(sec_key) - 1)) == NULL) {
+ zend_release_properties(opt_props);
zend_argument_value_error(4, "must have key \"%s\"", sec_key);
RETURN_THROWS();
}
- if ((usec = zend_hash_str_find(opt_ht, usec_key, sizeof(usec_key) - 1)) == NULL) {
+ if ((usec = zend_hash_str_find_ind(opt_ht, usec_key, sizeof(usec_key) - 1)) == NULL) {
+ zend_release_properties(opt_props);
zend_argument_value_error(4, "must have key \"%s\"", usec_key);
RETURN_THROWS();
}
zend_long valsec = zval_get_long(sec);
zend_long valusec = zval_get_long(usec);
+ zend_release_properties(opt_props);
#ifndef PHP_WIN32
tv.tv_sec = valsec;
tv.tv_usec = valusec;
diff --git a/ext/sockets/tests/gh24748.phpt b/ext/sockets/tests/gh24748.phpt
new file mode 100644
index 00000000000..84f2f127d00
--- /dev/null
+++ b/ext/sockets/tests/gh24748.phpt
@@ -0,0 +1,64 @@
+--TEST--
+GH-24748 (socket_set_option() crashes when the value is an object with declared properties)
+--EXTENSIONS--
+sockets
+--FILE--
+<?php
+class Linger {
+ public $l_onoff = 1;
+ public $l_linger = 5;
+}
+
+class Timeout {
+ public $sec = 1;
+ public int $usec;
+}
+
+$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_LINGER, new Linger()));
+var_dump(socket_get_option($socket, SOL_SOCKET, SO_LINGER)["l_linger"]);
+
+try {
+ socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, new Timeout());
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+$timeout = new Timeout();
+$timeout->usec = 0;
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout));
+var_dump(socket_get_option($socket, SOL_SOCKET, SO_SNDTIMEO));
+
+$linger = new stdClass();
+$linger->l_onoff = new stdClass();
+$linger->l_linger = 5;
+set_error_handler(function () use ($linger) {
+ $tmp = (array) $linger;
+ $linger->extra = 1;
+ unset($tmp);
+ return true;
+});
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_LINGER, $linger));
+restore_error_handler();
+
+try {
+ socket_set_option($socket, SOL_SOCKET, SO_LINGER, new ArrayObject(['l_onoff' => 1, 'l_linger' => 70000]));
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+bool(true)
+int(5)
+socket_set_option(): Argument #4 ($value) must have key "usec"
+bool(true)
+bool(true)
+array(2) {
+ ["sec"]=>
+ int(1)
+ ["usec"]=>
+ int(0)
+}
+bool(true)
+socket_set_option(): Argument #4 ($value) "l_linger" must be between 0 and 65535