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); } }