diff --git a/NEWS b/NEWS
index a75d304276f3..3db6bc520e84 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,10 @@ PHP NEWS
- DOM:
. Fixed bug GH-22624 (use-after-free via DOMNameSpaceNode after
DOMDocument::xinclude()). (David Carlier)
+ . Fixed a use-after-free when cloning a DOMNameSpaceNode after
+ DOMDocument::xinclude(). (iliaal)
+ . Fixed a crash in DOMXPath when a php:function callback receives a nodeset
+ and a later callback returns a node from another document. (iliaal)
- PDO_PGSQL:
. Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
@@ -25,6 +29,12 @@ PHP NEWS
left busy for the next fetch, and rows delivered from a result another
statement took over. (KentarouTakeda)
+- Intl:
+ . Fixed grapheme_strrev() treating UBRK_DONE as a byte index and leaving
+ the result without a terminating NUL. (iliaal)
+ . Fixed a double-free when IntlGregorianCalendar construction fails after
+ the ICU constructor adopts the TimeZone. (iliaal)
+
- Phar:
. Fixed Phar archives being automatically detected when ".phar" only occurs
in a directory name or is not a filename extension in an included file's
diff --git a/configure.ac b/configure.ac
index 3229e0dac040..b1ff51bcc2d1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1224,7 +1224,15 @@ AS_CASE([$host_cpu],
AS_CASE([$host_os],
[darwin*], [fiber_os="mac"],
- [aix*|os400*], [fiber_os="aix"],
+ [aix*|os400*], [
+ fiber_os="aix"
+ dnl AIX triplets start w/ powerpc- even though it may be 64-bit (as AIX
+ dnl has a multiple bitness world). Check if the build is 64-bit, or we
+ dnl end up using the wrong fibre asm (w/o forcing a powerpc64 triplet)
+ AS_IF([test "$ac_cv_sizeof_long" -eq 8], [
+ fiber_cpu="ppc64"
+ ], [])
+ ],
[freebsd*], [fiber_os="freebsd"],
[midipix], [fiber_os="midipix"],
[fiber_os="other"]
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 467271943275..158534530c3e 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -145,7 +145,7 @@ static HashTable dom_xpath_prop_handlers;
static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type);
static void dom_object_namespace_node_free_storage(zend_object *object);
-static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original);
+static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original, xmlDocPtr fallback_doc);
typedef zend_result (*dom_read_t)(dom_object *obj, zval *retval);
typedef zend_result (*dom_write_t)(dom_object *obj, zval *newval);
@@ -725,7 +725,8 @@ static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
xmlNodePtr original_node = dom_object_get_node(&intern->dom);
if (original_node != NULL) {
ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL);
- xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns);
+ xmlNodePtr parent = intern->parent_intern ? dom_object_get_node(intern->parent_intern) : NULL;
+ xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(parent, original_node->ns, original_node->doc);
dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node);
}
@@ -2262,15 +2263,16 @@ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) {
}
/* }}} end dom_get_nsdecl */
-static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original)
+static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original, xmlDocPtr fallback_doc)
{
xmlNodePtr attrp;
+ xmlDocPtr doc = nodep ? nodep->doc : fallback_doc;
xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL);
if (original->prefix) {
curns->prefix = xmlStrdup(original->prefix);
- attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST original->prefix, original->href);
+ attrp = xmlNewDocNode(doc, NULL, BAD_CAST original->prefix, original->href);
} else {
- attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST "xmlns", original->href);
+ attrp = xmlNewDocNode(doc, NULL, BAD_CAST "xmlns", original->href);
}
attrp->type = XML_NAMESPACE_DECL;
attrp->parent = nodep;
@@ -2281,7 +2283,7 @@ static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep,
/* Note: Assumes the additional lifetime was already added in the caller. */
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
{
- xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original);
+ xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original, NULL);
php_dom_create_object(attrp, return_value, parent_intern);
/* This object must exist, because we just created an object for it via php_dom_create_object(). */
php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern;
diff --git a/ext/dom/tests/dom_namespacenode_clone_xinclude.phpt b/ext/dom/tests/dom_namespacenode_clone_xinclude.phpt
new file mode 100644
index 000000000000..33efc0a73b44
--- /dev/null
+++ b/ext/dom/tests/dom_namespacenode_clone_xinclude.phpt
@@ -0,0 +1,44 @@
+--TEST--
+DOMNameSpaceNode clone after xinclude does not use a dangling parent
+--EXTENSIONS--
+dom
+--FILE--
+');
+$href = 'file:///' . ltrim(str_replace('\\', '/', $included), '/');
+
+$doc = new DOMDocument();
+$doc->loadXML('
+
+
+');
+
+$xpath = new DOMXPath($doc);
+$xpath->registerNamespace('xi', 'http://www.w3.org/2001/XInclude');
+$xi = $xpath->query('//xi:include')->item(0);
+$ns = $xpath->query('namespace::local', $xi)->item(0);
+
+$live = clone $ns;
+echo "live clone: ", $live->nodeName, "\n";
+echo "live parent: ", $live->parentNode->nodeName, "\n";
+
+$doc->xinclude();
+
+$clone = clone $ns;
+echo "after xinclude: ", $clone->nodeName, "\n";
+var_dump($clone->parentNode);
+var_dump($clone->parentElement);
+var_dump($clone->isConnected);
+?>
+--CLEAN--
+
+--EXPECT--
+live clone: xmlns:local
+live parent: xi:include
+after xinclude: xmlns:local
+NULL
+NULL
+bool(false)
diff --git a/ext/dom/tests/xpath_php_function_foreign_doc.phpt b/ext/dom/tests/xpath_php_function_foreign_doc.phpt
new file mode 100644
index 000000000000..4e4b2b2c2822
--- /dev/null
+++ b/ext/dom/tests/xpath_php_function_foreign_doc.phpt
@@ -0,0 +1,40 @@
+--TEST--
+DOMXPath: php:function nodeset args plus a foreign-document return must not treat arrays as DOM objects
+--EXTENSIONS--
+dom
+--FILE--
+loadXML('1');
+$doc2 = new DOMDocument();
+$doc2->loadXML('2');
+
+$xp = new DOMXPath($doc1);
+$xp->registerNamespace('php', 'http://php.net/xpath');
+$xp->registerPhpFunctions();
+
+function uses_nodeset($nodes) {
+ return true;
+}
+
+function foreign() {
+ global $doc2;
+ return $doc2->documentElement;
+}
+
+$xp->query('//a[php:function("uses_nodeset", //a)]');
+$res = $xp->query('php:function("foreign")');
+echo "count: ";
+var_dump($res->length);
+$n = $res->item(0);
+echo "name: ";
+var_dump($n->nodeName);
+echo "owner is doc2: ";
+var_dump($n->ownerDocument === $doc2);
+echo "done\n";
+?>
+--EXPECT--
+count: int(1)
+name: string(4) "root"
+owner is doc2: bool(true)
+done
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index dc9e1b852fae..a35d343b50d9 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -33,6 +33,25 @@
#ifdef LIBXML_XPATH_ENABLED
+static dom_object *dom_xpath_intern_from_entry(zval *entry, xmlDocPtr doc)
+{
+ if (Z_TYPE_P(entry) == IS_OBJECT) {
+ dom_object *obj = Z_DOMOBJ_P(entry);
+ if (obj->document && obj->document->ptr == doc) {
+ return obj;
+ }
+ } else if (Z_TYPE_P(entry) == IS_ARRAY) {
+ zval *inner;
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(entry), inner) {
+ dom_object *obj = dom_xpath_intern_from_entry(inner, doc);
+ if (obj) {
+ return obj;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ return NULL;
+}
+
static dom_object *dom_xpath_intern_for_doc(dom_xpath_object *xpath_obj, xmlDocPtr doc)
{
if (xpath_obj->dom.document && xpath_obj->dom.document->ptr == doc) {
@@ -42,8 +61,8 @@ static dom_object *dom_xpath_intern_for_doc(dom_xpath_object *xpath_obj, xmlDocP
if (node_list) {
zval *entry;
ZEND_HASH_PACKED_FOREACH_VAL(node_list, entry) {
- dom_object *obj = Z_DOMOBJ_P(entry);
- if (obj->document && obj->document->ptr == doc) {
+ dom_object *obj = dom_xpath_intern_from_entry(entry, doc);
+ if (obj) {
return obj;
}
} ZEND_HASH_FOREACH_END();
diff --git a/ext/intl/calendar/gregoriancalendar_methods.cpp b/ext/intl/calendar/gregoriancalendar_methods.cpp
index aeb728e29de1..9573bdf5d9e7 100644
--- a/ext/intl/calendar/gregoriancalendar_methods.cpp
+++ b/ext/intl/calendar/gregoriancalendar_methods.cpp
@@ -165,7 +165,6 @@ static void _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAMETERS, bool
if (U_FAILURE(status)) {
intl_error_set(NULL, status, "error creating ICU "
"GregorianCalendar from time zone and locale");
- delete tz;
if (!is_constructor) {
zval_ptr_dtor(return_value);
RETVAL_NULL();
diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp
index 5e614be6ae72..a1daae84db9c 100644
--- a/ext/intl/grapheme/grapheme_string.cpp
+++ b/ext/intl/grapheme/grapheme_string.cpp
@@ -1175,6 +1175,9 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev)
current = ZSTR_LEN(string);
for (end = pstr; pos != UBRK_DONE; ) {
pos = ubrk_previous(bi);
+ if (pos == UBRK_DONE) {
+ break;
+ }
end_len = current - pos;
for (int32_t j = 0; j < end_len; j++) {
*p++ = *(pstr + pos + j);
@@ -1182,6 +1185,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev)
current = pos;
}
ubrk_end:
+ ZSTR_VAL(ret)[ZSTR_LEN(ret)] = '\0';
RETVAL_NEW_STR(ret);
ubrk_close(bi);
close:
diff --git a/ext/intl/tests/grapheme_strrev_ubrk_done.phpt b/ext/intl/tests/grapheme_strrev_ubrk_done.phpt
new file mode 100644
index 000000000000..6f70f7cd1f7f
--- /dev/null
+++ b/ext/intl/tests/grapheme_strrev_ubrk_done.phpt
@@ -0,0 +1,25 @@
+--TEST--
+grapheme_strrev() stops at UBRK_DONE instead of using it as a byte index
+--EXTENSIONS--
+intl
+--FILE--
+
+--EXPECT--
+3 3 636261
+1 1 61
+9 9 e5baa7e4b88be59c9f
+9 9 65747962006c6c756e
diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
index d6afbb049773..dad8bc52ddd1 100644
--- a/ext/mysqli/tests/fake_server.inc
+++ b/ext/mysqli/tests/fake_server.inc
@@ -560,6 +560,32 @@ class my_mysqli_fake_server_conn
}
}
+ private function read_bytes(int $length): string
+ {
+ $data = '';
+ while (strlen($data) < $length) {
+ $chunk = fread($this->conn, $length - strlen($data));
+ if ($chunk === false || $chunk === '') {
+ throw new RuntimeException("Failed to read $length bytes from the client");
+ }
+ $data .= $chunk;
+ }
+ return $data;
+ }
+
+ public function read_packets(int $packet_count): void
+ {
+ $data = '';
+ for ($i = 0; $i < $packet_count; $i++) {
+ $header = $this->read_bytes(4);
+ $packet_length = ord($header[0])
+ | (ord($header[1]) << 8)
+ | (ord($header[2]) << 16);
+ $data .= $header . $this->read_bytes($packet_length);
+ }
+ fprintf(STDERR, "[*] Received: %s\n", bin2hex($data));
+ }
+
public function close()
{
fclose($this->conn);
@@ -732,7 +758,7 @@ function my_mysqli_test_stmt_response_row_over_read_two_fields(
$conn->packets_to_bytes($rh),
"Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]"
);
- $conn->read(65536);
+ $conn->read_packets(2);
}
function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void
@@ -778,15 +804,15 @@ function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_co
function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
{
$conn->send_server_greetings();
- $conn->read();
+ $conn->read_packets(1);
$conn->send_server_ok();
- $conn->read();
+ $conn->read_packets(1);
$field_names = array_keys(my_mysqli_data_fields());
foreach ($field_names as $field_name) {
$conn->send_server_stmt_prepare_data_response($field_name);
- $conn->read(65536);
+ $conn->read_packets(1);
$conn->send_server_stmt_execute_data_response($field_name);
- $conn->read(65536);
+ $conn->read_packets(2);
}
}