diff --git a/ext/dom/node.c b/ext/dom/node.c index bcf4ee487d38d..a5bc7d00ffa12 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -1786,4 +1786,25 @@ PHP_METHOD(DOMNode, getLineNo) } /* }}} */ +/** + * We want to block the serialization and unserialization of DOM classes. + * However, using @not-serializable makes the child classes also not serializable, even if the user implements the methods. + * So instead, we implement the methods wherein we throw exceptions. + * The reason we choose these methods is because: + * - If the user implements __serialize / __unserialize, the respective throwing methods are not called. + * - If the user implements __sleep / __wakeup, then it's also not a problem because they will not enter the throwing methods. + */ + +PHP_METHOD(DOMNode, __sleep) +{ + zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); +} + +PHP_METHOD(DOMNode, __wakeup) +{ + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); +} + #endif diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index bbf7bad0f1491..3fb2909a6f922 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -56,7 +56,6 @@ public function after(...$nodes): void; public function replaceWith(...$nodes): void; } -/** @not-serializable */ class DOMNode { /** @readonly */ @@ -104,6 +103,10 @@ class DOMNode public string $textContent; + public function __sleep(): array {} + + public function __wakeup(): void {} + /** @return DOMNode|false */ public function appendChild(DOMNode $node) {} @@ -156,7 +159,6 @@ public function removeChild(DOMNode $child) {} public function replaceChild(DOMNode $node, DOMNode $child) {} } -/** @not-serializable */ class DOMNameSpaceNode { /** @readonly */ @@ -182,6 +184,12 @@ class DOMNameSpaceNode /** @readonly */ public ?DOMNode $parentNode; + + /** @implementation-alias DOMNode::__sleep */ + public function __sleep(): array {} + + /** @implementation-alias DOMNode::__wakeup */ + public function __wakeup(): void {} } class DOMImplementation diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 1be65cb75d16b..edcc883aba57e 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 20a0ff883af3bbf073d9c8bc8246646ffafe7818 */ + * Stub hash: 203760d1cf0e063ffd9abe743a0e24a97985767e */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -28,6 +28,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_DOMChildNode_replaceWith arginfo_class_DOMParentNode_append +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNode___sleep, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_DOMNode___wakeup arginfo_class_DOMChildNode_remove + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_appendChild, 0, 0, 1) ZEND_ARG_OBJ_INFO(0, node, DOMNode, 0) ZEND_END_ARG_INFO() @@ -100,6 +105,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_replaceChild, 0, 0, 2) ZEND_ARG_OBJ_INFO(0, child, DOMNode, 0) ZEND_END_ARG_INFO() +#define arginfo_class_DOMNameSpaceNode___sleep arginfo_class_DOMNode___sleep + +#define arginfo_class_DOMNameSpaceNode___wakeup arginfo_class_DOMChildNode_remove + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMImplementation_getFeature, 0, 2, IS_NEVER, 0) ZEND_ARG_TYPE_INFO(0, feature, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, version, IS_STRING, 0) @@ -491,6 +500,8 @@ ZEND_END_ARG_INFO() ZEND_FUNCTION(dom_import_simplexml); ZEND_METHOD(DOMCdataSection, __construct); ZEND_METHOD(DOMComment, __construct); +ZEND_METHOD(DOMNode, __sleep); +ZEND_METHOD(DOMNode, __wakeup); ZEND_METHOD(DOMNode, appendChild); ZEND_METHOD(DOMNode, C14N); ZEND_METHOD(DOMNode, C14NFile); @@ -672,6 +683,8 @@ static const zend_function_entry class_DOMChildNode_methods[] = { static const zend_function_entry class_DOMNode_methods[] = { + ZEND_ME(DOMNode, __sleep, arginfo_class_DOMNode___sleep, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNode, __wakeup, arginfo_class_DOMNode___wakeup, ZEND_ACC_PUBLIC) ZEND_ME(DOMNode, appendChild, arginfo_class_DOMNode_appendChild, ZEND_ACC_PUBLIC) ZEND_ME(DOMNode, C14N, arginfo_class_DOMNode_C14N, ZEND_ACC_PUBLIC) ZEND_ME(DOMNode, C14NFile, arginfo_class_DOMNode_C14NFile, ZEND_ACC_PUBLIC) @@ -694,6 +707,8 @@ static const zend_function_entry class_DOMNode_methods[] = { static const zend_function_entry class_DOMNameSpaceNode_methods[] = { + ZEND_MALIAS(DOMNode, __sleep, __sleep, arginfo_class_DOMNameSpaceNode___sleep, ZEND_ACC_PUBLIC) + ZEND_MALIAS(DOMNode, __wakeup, __wakeup, arginfo_class_DOMNameSpaceNode___wakeup, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -989,7 +1004,6 @@ static zend_class_entry *register_class_DOMNode(void) INIT_CLASS_ENTRY(ce, "DOMNode", class_DOMNode_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; zval property_nodeName_default_value; ZVAL_UNDEF(&property_nodeName_default_value); @@ -1104,7 +1118,6 @@ static zend_class_entry *register_class_DOMNameSpaceNode(void) INIT_CLASS_ENTRY(ce, "DOMNameSpaceNode", class_DOMNameSpaceNode_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; zval property_nodeName_default_value; ZVAL_UNDEF(&property_nodeName_default_value); diff --git a/ext/dom/tests/gh8996.phpt b/ext/dom/tests/gh8996.phpt new file mode 100644 index 0000000000000..4175f9acce160 --- /dev/null +++ b/ext/dom/tests/gh8996.phpt @@ -0,0 +1,84 @@ +--TEST-- +GH-8996: DOMNode serialization on PHP ^8.1 +--EXTENSIONS-- +dom +--FILE-- +xmlData = $this->saveXML(); + return ['xmlData']; + } + + public function __wakeup(): void + { + $this->loadXML($this->xmlData); + } +} + +$dom = new SerializableDomDocumentSleepWakeup('1.0', 'UTF-8'); +$dom->loadXML('value'); + +$serialized = serialize($dom); +var_dump($serialized); +$unserialized = unserialize($serialized); + +echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}"; + +echo "=== __serialize and __unserialize ===\n"; + +class SerializableDomDocumentSerializeUnserialize extends DOMDocument +{ + public function __serialize(): array + { + return ['xmlData' => $this->saveXML()]; + } + + public function __unserialize(array $data): void + { + $this->loadXML($data['xmlData']); + } +} + +$dom = new SerializableDomDocumentSerializeUnserialize('1.0', 'UTF-8'); +$dom->loadXML('value'); + +$serialized = serialize($dom); +$unserialized = unserialize($serialized); + +echo "Serialized:\n-----------\n$serialized\n-----------\nRestored:\n-----------\n{$unserialized->saveXml()}"; + +?> +--EXPECTF-- +=== __sleep and __wakeup === +string(144) "O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:" +value +";}" +Serialized: +----------- +O:34:"SerializableDomDocumentSleepWakeup":1:{s:43:"%0SerializableDomDocumentSleepWakeup%0xmlData";s:39:" +value +";} +----------- +Restored: +----------- + +value +=== __serialize and __unserialize === +Serialized: +----------- +O:43:"SerializableDomDocumentSerializeUnserialize":1:{s:7:"xmlData";s:39:" +value +";} +----------- +Restored: +----------- + +value diff --git a/ext/dom/tests/not_unserializable.phpt b/ext/dom/tests/not_unserializable.phpt new file mode 100644 index 0000000000000..30ae64feb5701 --- /dev/null +++ b/ext/dom/tests/not_unserializable.phpt @@ -0,0 +1,29 @@ +--TEST-- +DOM classes are not unserializable +--EXTENSIONS-- +dom +--FILE-- +getMessage(), "\n"; + } +} + +?> +--EXPECT-- +Unserialization of 'DOMXPath' is not allowed +Unserialization of 'DOMDocument' is not allowed +Unserialization of 'DOMNode' is not allowed +Unserialization of 'DOMNameSpaceNode' is not allowed