From 5b00437a09de5bb0ae1631527d519e6ad9851a33 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 9 Jul 2026 11:39:37 -0700 Subject: [PATCH 1/7] Add regression tests for `ReflectionConstant::__toString()` with null bytes So that when the output is fixed the change can be confirmed in tests --- .../tests/ReflectionConstant_null_byte_value.phpt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ext/reflection/tests/ReflectionConstant_null_byte_value.phpt diff --git a/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt b/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt new file mode 100644 index 000000000000..5e1b855eed05 --- /dev/null +++ b/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-22658: ReflectionConstant with a string value with a null byte +--FILE-- +getValue() ); + +?> +--EXPECTF-- +Constant [ string DEMO ] { f } +string(4) "f%0oo" From ed3e06a6738b5ab9b7ed8e92cda79832cd172ea1 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 9 Jul 2026 11:41:30 -0700 Subject: [PATCH 2/7] GH-22658: avoid truncation on null bytes in `ReflectionConstant::__toString()` --- ext/reflection/php_reflection.c | 2 +- ext/reflection/tests/ReflectionConstant_null_byte_value.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index e747a642778c..661b1eb1f3a4 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -585,7 +585,7 @@ static void _const_string(smart_str *str, const char *name, zval *value, const c if (Z_TYPE_P(value) == IS_ARRAY) { smart_str_append(str, ZSTR_KNOWN(ZEND_STR_ARRAY_CAPITALIZED)); } else if (Z_TYPE_P(value) == IS_STRING) { - smart_str_appends(str, Z_STRVAL_P(value)); + smart_str_append(str, Z_STR_P(value)); } else { zend_string *tmp_value_str; zend_string *value_str = zval_get_tmp_string(value, &tmp_value_str); diff --git a/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt b/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt index 5e1b855eed05..8b6a8e85c2fc 100644 --- a/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt +++ b/ext/reflection/tests/ReflectionConstant_null_byte_value.phpt @@ -10,5 +10,5 @@ var_dump( $r->getValue() ); ?> --EXPECTF-- -Constant [ string DEMO ] { f } +Constant [ string DEMO ] { f%0oo } string(4) "f%0oo" From 834bdcedd1c89cdbc722567a6c4f23c3df1d2554 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 10 Jul 2026 07:22:08 -0700 Subject: [PATCH 3/7] NEWS --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 2d0abd7eb42f..7324e5c591aa 100644 --- a/NEWS +++ b/NEWS @@ -65,6 +65,8 @@ PHP NEWS ReflectionParameter::__construct()). (jorgsowa) . Fixed bug GH-22441 (ReflectionClass::hasProperty() and getProperty() ignore dynamic properties shadowing a private parent property). (iliaal) + . Fixed bug GH-22658 (ReflectionConstant::__toString() with a string value + with null bytes truncates output). (DanielEScherzer) - Session: . Fixed bug GH-21314 (Different session garbage collector behavior between From 032b2d392de231c0cdbbc886be5f00fa59dc98c1 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 10 Jul 2026 16:52:26 +0100 Subject: [PATCH 4/7] Fixed __debugInfo() usage on PHP 8.5 and later for DatePeriod (#22677) --- ext/date/php_date.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index bcf4bcdded06..ad4b4a51101e 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -6076,6 +6076,17 @@ static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string * static HashTable *date_period_get_properties_for(zend_object *object, zend_prop_purpose purpose) { + if (purpose == ZEND_PROP_PURPOSE_DEBUG) { + if (object->ce->__debugInfo) { + int is_temp = 0; + HashTable *ht = zend_std_get_debug_info(object, &is_temp); + if (ht && !is_temp) { + GC_TRY_ADDREF(ht); + } + return ht; + } + } + php_period_obj *period_obj = php_period_obj_from_obj(object); HashTable *props = zend_array_dup(zend_std_get_properties(object)); if (!period_obj->initialized) { From 955a2ce5195ea8ab94ba7a5afadd828e74bce5b6 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 10 Jul 2026 11:02:22 -0700 Subject: [PATCH 5/7] [8.5] NEWS: add entry for bugfix from GH-22659 --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 3851713db0db..ecd2c1267112 100644 --- a/NEWS +++ b/NEWS @@ -82,6 +82,8 @@ PHP NEWS ReflectionParameter::__construct()). (jorgsowa) . Fixed bug GH-22441 (ReflectionClass::hasProperty() and getProperty() ignore dynamic properties shadowing a private parent property). (iliaal) + . Fixed bug GH-22658 (ReflectionConstant::__toString() with a string value + with null bytes truncates output). (DanielEScherzer) - Session: . Fixed bug GH-21314 (Different session garbage collector behavior between From 65a69b4a09ff454e0ed2c597d23f4c9dc52d1507 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 10 Jul 2026 15:33:45 -0400 Subject: [PATCH 6/7] Fix GH-22671: assert.bail must not unwind with a pending exception (#22679) zend_throw_unwind_exit() requires !EG(exception), but assert.bail called it right after zend_exception_error(), which can itself re-throw while printing the callback's exception (a throwing __toString, a throwing __destruct when the exception is released, or the call-stack guard re-tripping at the stack limit). Only throw the unwind exit when no exception is pending; otherwise let the re-thrown exception propagate, matching exit() and phar. Fixes GH-22671 --- NEWS | 2 ++ ext/standard/assert.c | 4 ++- ext/standard/tests/assert/gh22671.phpt | 40 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/assert/gh22671.phpt diff --git a/NEWS b/NEWS index ac709587f699..cd259f06660e 100644 --- a/NEWS +++ b/NEWS @@ -80,6 +80,8 @@ PHP NEWS - Standard: . Fixed sleep() and usleep() to reject values that overflow the underlying unsigned int timeout. (Weilin Du) + . Fixed bug GH-22671 (assert.bail aborts the process when the assert callback + throws an exception whose reporting re-throws). (iliaal) - Streams: . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL diff --git a/ext/standard/assert.c b/ext/standard/assert.c index a976aeda1494..618f69a2cf74 100644 --- a/ext/standard/assert.c +++ b/ext/standard/assert.c @@ -245,7 +245,9 @@ PHP_FUNCTION(assert) * exception so we can avoid bailout and use unwind_exit. */ zend_exception_error(EG(exception), E_WARNING); } - zend_throw_unwind_exit(); + if (!EG(exception)) { + zend_throw_unwind_exit(); + } RETURN_THROWS(); } else { RETURN_FALSE; diff --git a/ext/standard/tests/assert/gh22671.phpt b/ext/standard/tests/assert/gh22671.phpt new file mode 100644 index 000000000000..3487b0687ccd --- /dev/null +++ b/ext/standard/tests/assert/gh22671.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22671 (assert.bail must not call zend_throw_unwind_exit with a pending exception) +--INI-- +zend.assertions=1 +assert.bail=1 +assert.exception=0 +assert.callback=cb +error_reporting=0 +--FILE-- + +--EXPECT-- +shutdown reached From 9073875eb98416595f1666df0f6a728bab2bc5c7 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 6 Jul 2026 23:25:57 +0100 Subject: [PATCH 7/7] ext/dom: Use-after-free with namespace nodes from XSLTProcessor::registerPHPFunctions(). Fix #22621 Namespace parents from an external document() were wrapped directly instead of through the proxy factory, so they skipped the copy that keeps non-main-document nodes alive. A DOMNameSpaceNode retained by userland then dangled once xsltFreeTransformContext() freed the external document. Route the namespace parent through the proxy factory too. close GH-22623 --- NEWS | 2 ++ ext/dom/xpath_callbacks.c | 6 ++-- ext/xsl/tests/gh22621.phpt | 63 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 ext/xsl/tests/gh22621.phpt diff --git a/NEWS b/NEWS index cd259f06660e..f40c6dfd8da6 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,8 @@ PHP NEWS . Fixed Dom\DtdNamedNodeMap integer dimension access so negative indexes return NULL and indexes outside the int range throw ValueError instead of returning the first entity or notation. (Weilin Du) + . Fixed bug GH-22623 (use after free with namespace nodes from + XSLTProcessor::registerFunctions())/ (David Carlier) - Exif: . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size" diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c index 622dd187dd84..349c304c9f25 100644 --- a/ext/dom/xpath_callbacks.c +++ b/ext/dom/xpath_callbacks.c @@ -347,15 +347,15 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui xmlNodePtr node = obj->nodesetval->nodeTab[j]; zval child; if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) { - xmlNodePtr nsparent = node->_private; xmlNsPtr original = (xmlNsPtr) node; /* Make sure parent dom object exists, so we can take an extra reference. */ zval parent_zval; /* don't destroy me, my lifetime is transferred to the fake namespace decl */ - php_dom_create_object(nsparent, &parent_zval, intern); + proxy_factory(node->_private, &parent_zval, intern, ctxt); dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval); + xmlNodePtr parent = dom_object_get_node(parent_intern); - php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern); + php_dom_create_fake_namespace_decl(parent, original, &child, parent_intern); } else { proxy_factory(node, &child, intern, ctxt); } diff --git a/ext/xsl/tests/gh22621.phpt b/ext/xsl/tests/gh22621.phpt new file mode 100644 index 000000000000..b31671eff630 --- /dev/null +++ b/ext/xsl/tests/gh22621.phpt @@ -0,0 +1,63 @@ +--TEST-- +GH-22621 (UAF via XSLTProcessor::registerPHPFunctions() retaining namespace nodes from an external document) +--EXTENSIONS-- +dom +xsl +--CREDITS-- +ExPatch-LLC +--FILE-- +secret'); +$uri = 'file:///' . ltrim(str_replace('\\', '/', $cDIR), '/') . '/gh22621_external.xml'; + +$stored_ns = null; + +function capture_ns($nodes) { + global $stored_ns; + foreach ($nodes as $node) { + if ($node instanceof DOMNameSpaceNode) { + $stored_ns = $node; + } + } + return "captured"; +} + +$xsl = new DOMDocument(); +$xsl->loadXML(<< + + + + + + + +XSL); + +$doc = new DOMDocument(); +$doc->loadXML(''); + +$proc = new XSLTProcessor(); +$proc->registerPHPFunctions(); +$proc->importStylesheet($xsl); +echo $proc->transformToXml($doc); + +var_dump($stored_ns->localName); +var_dump($stored_ns->namespaceURI); +var_dump($stored_ns->parentNode->localName); +var_dump($stored_ns->ownerDocument instanceof DOMDocument); +?> +--CLEAN-- + +--EXPECTF-- + +captured +string(6) "custom" +string(10) "urn:custom" +string(4) "root" +bool(true)