Commit ff480bce5c4 for php.net

commit ff480bce5c44e3bfe62bec6a689d62d5cdc20793
Author: Weilin Du <weilindu@php.net>
Date:   Wed Jul 8 20:10:26 2026 +0800

    ext/dom: Fix Dom\DtdNamedNodeMap index overflow in dimension access (#22630)

    Dom\DtdNamedNodeMap dimension access could overflow the index when reading DTD
    entities or notations. This commit fix it.

diff --git a/NEWS b/NEWS
index 8971e7d317a..cd396479e3e 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,9 @@ PHP                                                                        NEWS
 - DOM:
   . Fixed bug GH-22570 (Stack overflow when serializing a deeply nested
     Dom\XMLDocument). (iliaal)
+  . 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)

 - Exif:
   . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
diff --git a/ext/dom/dom_iterators.c b/ext/dom/dom_iterators.c
index f97a9ec825d..7566dd8b61a 100644
--- a/ext/dom/dom_iterators.c
+++ b/ext/dom/dom_iterators.c
@@ -79,14 +79,22 @@ void dom_free_notation(xmlEntityPtr entity) /* {{{ */
 }
 /* }}} */

-xmlNodePtr php_dom_libxml_hash_iter(xmlHashTable *ht, int index)
+xmlNodePtr php_dom_libxml_hash_iter(xmlHashTable *ht, zend_long index)
 {
 	int htsize;

+	if (index < 0) {
+		return NULL;
+	}
+	if (UNEXPECTED(ZEND_LONG_INT_OVFL(index))) {
+		zend_value_error("must be between 0 and %d", INT_MAX);
+		return NULL;
+	}
+
 	if ((htsize = xmlHashSize(ht)) > 0 && index < htsize) {
 		nodeIterator iter;
 		iter.cur = 0;
-		iter.index = index;
+		iter.index = (int) index;
 		iter.node = NULL;
 		xmlHashScan(ht, itemHashScanner, &iter);
 		return iter.node;
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index 1b19aeee3ba..6f4b5075b65 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -127,7 +127,7 @@ bool dom_node_is_read_only(const xmlNode *node);
 bool dom_node_children_valid(const xmlNode *node);
 xmlNodePtr create_notation(xmlDtdPtr parent_dtd, const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID);
 void dom_free_notation(xmlEntityPtr entity);
-xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index);
+xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, zend_long index);
 zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
 void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce);
 xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern);
diff --git a/ext/dom/tests/modern/xml/DtdNamedNodeMap_dimension_index_overflow_64.phpt b/ext/dom/tests/modern/xml/DtdNamedNodeMap_dimension_index_overflow_64.phpt
new file mode 100644
index 00000000000..cc2d7a06a2c
--- /dev/null
+++ b/ext/dom/tests/modern/xml/DtdNamedNodeMap_dimension_index_overflow_64.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Dom\DtdNamedNodeMap dimension access handles invalid integer indexes
+--EXTENSIONS--
+dom
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE == 4) {
+    die("skip this test is for 64bit platform only");
+}
+?>
+--FILE--
+<?php
+$doc = Dom\XMLDocument::createFromString(<<<XML
+<!DOCTYPE root [
+<!ENTITY e "entity">
+<!NOTATION n SYSTEM "notation">
+]>
+<root/>
+XML);
+
+$overflow = 4294967296;
+
+function dump_access(Closure $callback): void {
+    try {
+        var_dump($callback()?->nodeName);
+    } catch (ValueError $e) {
+        echo $e->getMessage(), PHP_EOL;
+    }
+}
+
+dump_access(fn() => $doc->doctype->entities[-1]);
+dump_access(fn() => $doc->doctype->entities[$overflow]);
+
+dump_access(fn() => $doc->doctype->notations[-1]);
+dump_access(fn() => $doc->doctype->notations[$overflow]);
+?>
+--EXPECT--
+NULL
+must be between 0 and 2147483647
+NULL
+must be between 0 and 2147483647