Commit 1a5a12e0f63 for php

commit 1a5a12e0f632e62f520f12eda36b694283ef24e0
Author: David Carlier <devnexen@gmail.com>
Date:   Sat Nov 7 18:33:26 2026 +0000

    Fix GH-25268: clone of Dom\Notation NULL dereference

    dom_clone_node() cannot copy notation nodes, so cloning a notation attached
    to a document left the clone without a document proxy and dereferenced it.
    Throw an Invalid State Error for modern DOM nodes when the clone fails
    instead of returning a half-initialized object.

    Close GH-25276

diff --git a/NEWS b/NEWS
index 668b5898c62..2c23cc8aa95 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,10 @@ PHP                                                                        NEWS
 - Curl:
   . Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire)

+- DOM:
+  . Fixed bug GH-25268 (NULL php_libxml_ref_obj dereference in
+    dom_objects_store_clone_obj when cloning Dom\Notation). (David Carlier)
+
 - MBString:
   . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with
     deeply nested arrays. (Eyüp Can Akman)
diff --git a/ext/dom/node.c b/ext/dom/node.c
index 49e400ef530..867d166edb4 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -1513,6 +1513,10 @@ PHP_METHOD(DOMNode, cloneNode)
 		if (clone_document && private_data != NULL) {
 			php_dom_private_data_destroy(private_data);
 		}
+		if (instanceof_function(intern->std.ce, dom_modern_node_class_entry)) {
+			php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
+			RETURN_THROWS();
+		}
 		RETURN_FALSE;
 	}

diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 445eacdfa19..ed300185eec 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -663,9 +663,11 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
 			xmlNodePtr cloned_node = dom_clone_node(php_dom_ns_mapper_from_private(private_data), node, node->doc, true);
 			if (cloned_node != NULL) {
 				dom_update_refcount_after_clone(intern, node, clone, cloned_node);
-			}
-			if (private_data != NULL) {
-				clone->document->private_data = php_dom_libxml_private_data_header(private_data);
+				if (private_data != NULL) {
+					clone->document->private_data = php_dom_libxml_private_data_header(private_data);
+				}
+			} else if (private_data != NULL && (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE)) {
+				php_dom_private_data_destroy(private_data);
 			}
 		}
 	}
diff --git a/ext/dom/tests/modern/xml/gh25268_declarations.phpt b/ext/dom/tests/modern/xml/gh25268_declarations.phpt
new file mode 100644
index 00000000000..ce814f2f7c4
--- /dev/null
+++ b/ext/dom/tests/modern/xml/gh25268_declarations.phpt
@@ -0,0 +1,27 @@
+--TEST--
+GH-25268 (NULL php_libxml_ref_obj dereference in dom_objects_store_clone_obj when cloning DTD declarations)
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+$dom = Dom\XMLDocument::createFromString('<!DOCTYPE root [<!ELEMENT root EMPTY><!ENTITY e "x">]><root/>');
+
+foreach ([$dom->doctype->entities->getNamedItem("e"), $dom->doctype->childNodes[0]] as $decl) {
+    $clone = clone $decl;
+    try {
+        var_dump($clone->nodeName);
+    } catch (DOMException $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+    try {
+        var_dump($decl->cloneNode());
+    } catch (DOMException $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+}
+?>
+--EXPECT--
+DOMException: Invalid State Error
+DOMException: Invalid State Error
+DOMException: Invalid State Error
+DOMException: Invalid State Error