From d816e1ed6df575b63933135fadaf564787f4011d Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 20:38:02 +0500 Subject: [PATCH 1/6] Fix GH-24748: socket_set_option() crash with declared object properties --- NEWS | 4 +++ ext/sockets/sockets.c | 8 +++--- ext/sockets/tests/gh24748.phpt | 49 ++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 ext/sockets/tests/gh24748.phpt diff --git a/NEWS b/NEWS index 5ea9ecbafb0b..3b0ee9ff9e90 100644 --- a/NEWS +++ b/NEWS @@ -155,6 +155,10 @@ PHP NEWS . Fixed bug GH-24488 (GC double-frees the SimpleXMLElement properties table). (evankford) +- Sockets: + . Fixed bug GH-24748 (socket_set_option() crashes when the value is an + object with declared properties). (lazerg) + - SPL: . Fixed bug GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c). (Lazizbek Ergashev) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 5e70df60726e..c6bfedc063f0 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1961,11 +1961,11 @@ PHP_FUNCTION(socket_set_option) 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_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_argument_value_error(4, "must have key \"%s\"", l_linger_key); RETURN_THROWS(); } @@ -2009,11 +2009,11 @@ PHP_FUNCTION(socket_set_option) 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_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_argument_value_error(4, "must have key \"%s\"", usec_key); RETURN_THROWS(); } diff --git a/ext/sockets/tests/gh24748.phpt b/ext/sockets/tests/gh24748.phpt new file mode 100644 index 000000000000..28549d385012 --- /dev/null +++ b/ext/sockets/tests/gh24748.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-24748 (socket_set_option() crashes when the value is an object with declared properties) +--EXTENSIONS-- +sockets +--FILE-- +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)); +?> +--EXPECT-- +bool(true) +array(2) { + ["l_onoff"]=> + int(1) + ["l_linger"]=> + int(5) +} +socket_set_option(): Argument #4 ($value) must have key "usec" +bool(true) +bool(true) +array(2) { + ["sec"]=> + int(1) + ["usec"]=> + int(0) +} From 1e99a2ab12f653929f3ef881bbd33296535d71e1 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 20:44:19 +0500 Subject: [PATCH 2/6] Read each socket option value before looking up the next one --- ext/sockets/sockets.c | 8 ++++---- ext/sockets/tests/gh24748.phpt | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c6bfedc063f0..eadf80bb005b 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1965,12 +1965,12 @@ PHP_FUNCTION(socket_set_option) zend_argument_value_error(4, "must have key \"%s\"", l_onoff_key); RETURN_THROWS(); } + zend_long val_lonoff = zval_get_long(l_onoff); + if ((l_linger = zend_hash_str_find_ind(opt_ht, l_linger_key, sizeof(l_linger_key) - 1)) == NULL) { 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); if (val_lonoff < 0 || val_lonoff > USHRT_MAX) { @@ -2013,12 +2013,12 @@ PHP_FUNCTION(socket_set_option) zend_argument_value_error(4, "must have key \"%s\"", sec_key); RETURN_THROWS(); } + zend_long valsec = zval_get_long(sec); + if ((usec = zend_hash_str_find_ind(opt_ht, usec_key, sizeof(usec_key) - 1)) == NULL) { 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); #ifndef PHP_WIN32 tv.tv_sec = valsec; diff --git a/ext/sockets/tests/gh24748.phpt b/ext/sockets/tests/gh24748.phpt index 28549d385012..2bd6380d06bd 100644 --- a/ext/sockets/tests/gh24748.phpt +++ b/ext/sockets/tests/gh24748.phpt @@ -29,6 +29,18 @@ $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 ($errno, $errstr) use ($linger) { + echo $errstr, "\n"; + for ($i = 0; $i < 100; $i++) { + $linger->{"p$i"} = $i; + } + return true; +}); +var_dump(socket_set_option($socket, SOL_SOCKET, SO_LINGER, $linger)); ?> --EXPECT-- bool(true) @@ -47,3 +59,5 @@ array(2) { ["usec"]=> int(0) } +Object of class stdClass could not be converted to int +bool(true) From 37088c00a613f2079b4624c23cad0ed329db3fb3 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 21:17:39 +0500 Subject: [PATCH 3/6] Keep the object property table alive while reading socket option values --- ext/sockets/sockets.c | 20 +++++++++++++------- ext/sockets/tests/gh24748.phpt | 10 ++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index eadf80bb005b..0c53160db701 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,26 @@ 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_ht = opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST); } } else { opt_ht = Z_ARRVAL_P(arg4); } 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(); } - zend_long val_lonoff = zval_get_long(l_onoff); - 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); @@ -2003,23 +2006,26 @@ PHP_FUNCTION(socket_set_option) zend_zval_value_name(arg4)); RETURN_THROWS(); } else { - opt_ht = Z_OBJPROP_P(arg4); + opt_ht = opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST); } } else { opt_ht = Z_ARRVAL_P(arg4); } 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(); } - zend_long valsec = zval_get_long(sec); - 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 index 2bd6380d06bd..8e11addc766f 100644 --- a/ext/sockets/tests/gh24748.phpt +++ b/ext/sockets/tests/gh24748.phpt @@ -33,11 +33,10 @@ 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 ($errno, $errstr) use ($linger) { - echo $errstr, "\n"; - for ($i = 0; $i < 100; $i++) { - $linger->{"p$i"} = $i; - } +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)); @@ -59,5 +58,4 @@ array(2) { ["usec"]=> int(0) } -Object of class stdClass could not be converted to int bool(true) From dfd1211e8fa540c5c0797499e31415c96414a8da Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 22:03:54 +0500 Subject: [PATCH 4/6] Only compare l_linger in the GH-24748 test, BSD reports l_onoff as 128 --- ext/sockets/tests/gh24748.phpt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ext/sockets/tests/gh24748.phpt b/ext/sockets/tests/gh24748.phpt index 8e11addc766f..a14db575c56a 100644 --- a/ext/sockets/tests/gh24748.phpt +++ b/ext/sockets/tests/gh24748.phpt @@ -16,7 +16,7 @@ class Timeout { $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)); +var_dump(socket_get_option($socket, SOL_SOCKET, SO_LINGER)["l_linger"]); try { socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, new Timeout()); @@ -43,12 +43,7 @@ var_dump(socket_set_option($socket, SOL_SOCKET, SO_LINGER, $linger)); ?> --EXPECT-- bool(true) -array(2) { - ["l_onoff"]=> - int(1) - ["l_linger"]=> - int(5) -} +int(5) socket_set_option(): Argument #4 ($value) must have key "usec" bool(true) bool(true) From 2f771be28bedc65080f34e22c0a70a6d583dc361 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 22:16:38 +0500 Subject: [PATCH 5/6] Fall back to an empty table when an object has no properties table --- ext/sockets/sockets.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 0c53160db701..f0435e557474 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1955,7 +1955,8 @@ 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 = opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST); + 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); @@ -2006,7 +2007,8 @@ PHP_FUNCTION(socket_set_option) zend_zval_value_name(arg4)); RETURN_THROWS(); } else { - opt_ht = opt_props = zend_get_properties_for(arg4, ZEND_PROP_PURPOSE_ARRAY_CAST); + 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); From 312d69eb51a14910622f9918142bba3166a9b28e Mon Sep 17 00:00:00 2001 From: lazerg Date: Tue, 27 Oct 2026 16:55:02 +0500 Subject: [PATCH 6/6] Use the l_linger key name in the SO_LINGER range error, drop NEWS entry --- NEWS | 4 ---- ext/sockets/sockets.c | 2 +- ext/sockets/tests/gh24748.phpt | 8 ++++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 3b0ee9ff9e90..5ea9ecbafb0b 100644 --- a/NEWS +++ b/NEWS @@ -155,10 +155,6 @@ PHP NEWS . Fixed bug GH-24488 (GC double-frees the SimpleXMLElement properties table). (evankford) -- Sockets: - . Fixed bug GH-24748 (socket_set_option() crashes when the value is an - object with declared properties). (lazerg) - - SPL: . Fixed bug GH-24264 (Unwind/GracefulExit discarded in spl_iterators.c). (Lazizbek Ergashev) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index f0435e557474..c1e7ead55465 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1983,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(); } diff --git a/ext/sockets/tests/gh24748.phpt b/ext/sockets/tests/gh24748.phpt index a14db575c56a..84f2f127d005 100644 --- a/ext/sockets/tests/gh24748.phpt +++ b/ext/sockets/tests/gh24748.phpt @@ -40,6 +40,13 @@ set_error_handler(function () use ($linger) { 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) @@ -54,3 +61,4 @@ array(2) { int(0) } bool(true) +socket_set_option(): Argument #4 ($value) "l_linger" must be between 0 and 65535