From c283c3ab0ba45d21b2b8745c1f9c7cbfe771c975 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 15 Jul 2023 17:33:52 +0200
Subject: [PATCH] Sanitize libxml2 globals before parsing
Fixes GHSA-3qrf-m4j2-pcrr.
To parse a document with libxml2, you first need to create a parsing context.
The parsing context contains parsing options (e.g. XML_NOENT to substitute
entities) that the application (in this case PHP) can set.
Unfortunately, libxml2 also supports providing default set options.
For example, if you call xmlSubstituteEntitiesDefault(1) then the XML_NOENT
option will be added to the parsing options every time you create a parsing
context **even if the application never requested XML_NOENT**.
Third party extensions can override these globals, in particular the
substitute entity global. This causes entity substitution to be
unexpectedly active.
Fix it by setting the parsing options to a sane known value.
For API calls that depend on global state we introduce
PHP_LIBXML_SANITIZE_GLOBALS() and PHP_LIBXML_RESTORE_GLOBALS().
For other APIs that work directly with a context we introduce
php_libxml_sanitize_parse_ctxt_options().
---
ext/dom/document.c | 15 ++++++++
ext/dom/documentfragment.c | 2 ++
...xml_global_state_entity_loader_bypass.phpt | 36 +++++++++++++++++++
ext/libxml/php_libxml.h | 36 +++++++++++++++++++
ext/simplexml/simplexml.c | 6 ++++
...xml_global_state_entity_loader_bypass.phpt | 36 +++++++++++++++++++
ext/soap/php_xml.c | 2 ++
ext/xml/compat.c | 2 ++
ext/xmlreader/php_xmlreader.c | 9 +++++
...xml_global_state_entity_loader_bypass.phpt | 35 ++++++++++++++++++
ext/xsl/xsltprocessor.c | 9 +++--
ext/zend_test/test.c | 17 +++++++++
ext/zend_test/test.stub.php | 4 +++
ext/zend_test/test_arginfo.h | 13 ++++++-
14 files changed, 216 insertions(+), 6 deletions(-)
create mode 100644 ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
create mode 100644 ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
create mode 100644 ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
diff --git a/ext/dom/document.c b/ext/dom/document.c
index fa6a6377ad5ce..02522b5014f26 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -1254,6 +1254,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
options |= XML_PARSE_NOBLANKS;
}
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
xmlCtxtUseOptions(ctxt, options);
ctxt->recovery = recover;
@@ -1548,7 +1549,9 @@ PHP_METHOD(DOMDocument, xinclude)
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+ PHP_LIBXML_SANITIZE_GLOBALS(xinclude);
err = xmlXIncludeProcessFlags(docp, (int)flags);
+ PHP_LIBXML_RESTORE_GLOBALS(xinclude);
/* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these
are added via xmlXIncludeProcess to mark beginning and ending of xincluded document
@@ -1586,6 +1589,7 @@ PHP_METHOD(DOMDocument, validate)
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+ PHP_LIBXML_SANITIZE_GLOBALS(validate);
cvp = xmlNewValidCtxt();
cvp->userData = NULL;
@@ -1597,6 +1601,7 @@ PHP_METHOD(DOMDocument, validate)
} else {
RETVAL_FALSE;
}
+ PHP_LIBXML_RESTORE_GLOBALS(validate);
xmlFreeValidCtxt(cvp);
@@ -1631,14 +1636,18 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+ PHP_LIBXML_SANITIZE_GLOBALS(new_parser_ctxt);
+
switch (type) {
case DOM_LOAD_FILE:
if (CHECK_NULL_PATH(source, source_len)) {
+ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
zend_argument_value_error(1, "must not contain any null bytes");
RETURN_THROWS();
}
valid_file = _dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
if (!valid_file) {
+ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
RETURN_FALSE;
}
@@ -1659,6 +1668,7 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
parser);
sptr = xmlSchemaParse(parser);
xmlSchemaFreeParserCtxt(parser);
+ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
if (!sptr) {
if (!EG(exception)) {
php_error_docref(NULL, E_WARNING, "Invalid Schema");
@@ -1679,11 +1689,13 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
valid_opts |= XML_SCHEMA_VAL_VC_I_CREATE;
}
+ PHP_LIBXML_SANITIZE_GLOBALS(validate);
xmlSchemaSetValidOptions(vptr, valid_opts);
xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
is_valid = xmlSchemaValidateDoc(vptr, docp);
xmlSchemaFree(sptr);
xmlSchemaFreeValidCtxt(vptr);
+ PHP_LIBXML_RESTORE_GLOBALS(validate);
if (is_valid == 0) {
RETURN_TRUE;
@@ -1754,12 +1766,14 @@ static void _dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int typ
return;
}
+ PHP_LIBXML_SANITIZE_GLOBALS(parse);
xmlRelaxNGSetParserErrors(parser,
(xmlRelaxNGValidityErrorFunc) php_libxml_error_handler,
(xmlRelaxNGValidityWarningFunc) php_libxml_error_handler,
parser);
sptr = xmlRelaxNGParse(parser);
xmlRelaxNGFreeParserCtxt(parser);
+ PHP_LIBXML_RESTORE_GLOBALS(parse);
if (!sptr) {
php_error_docref(NULL, E_WARNING, "Invalid RelaxNG");
RETURN_FALSE;
@@ -1858,6 +1872,7 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
ctxt->sax->error = php_libxml_ctx_error;
ctxt->sax->warning = php_libxml_ctx_warning;
}
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
if (options) {
htmlCtxtUseOptions(ctxt, (int)options);
}
diff --git a/ext/dom/documentfragment.c b/ext/dom/documentfragment.c
index 051416792d349..9a91d4dfcc012 100644
--- a/ext/dom/documentfragment.c
+++ b/ext/dom/documentfragment.c
@@ -114,7 +114,9 @@ PHP_METHOD(DOMDocumentFragment, appendXML) {
}
if (data) {
+ PHP_LIBXML_SANITIZE_GLOBALS(parse);
err = xmlParseBalancedChunkMemory(nodep->doc, NULL, NULL, 0, (xmlChar *) data, &lst);
+ PHP_LIBXML_RESTORE_GLOBALS(parse);
if (err != 0) {
RETURN_FALSE;
}
diff --git a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
new file mode 100644
index 0000000000000..b28afd4694eb0
--- /dev/null
+++ b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
@@ -0,0 +1,36 @@
+--TEST--
+GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+--SKIPIF--
+
+--FILE--
+ %bork;]>";
+
+libxml_use_internal_errors(true);
+
+function parseXML($xml) {
+ $doc = new DOMDocument();
+ @$doc->loadXML($xml);
+ $doc->createDocumentFragment()->appendXML("&bork;");
+ foreach (libxml_get_errors() as $error) {
+ var_dump(trim($error->message));
+ }
+}
+
+parseXML($xml);
+zend_test_override_libxml_global_state();
+parseXML($xml);
+
+echo "Done\n";
+
+?>
+--EXPECT--
+string(25) "Entity 'bork' not defined"
+string(25) "Entity 'bork' not defined"
+string(25) "Entity 'bork' not defined"
+Done
diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
index 85ffcd396958b..d0ce7cec7144c 100644
--- a/ext/libxml/php_libxml.h
+++ b/ext/libxml/php_libxml.h
@@ -118,6 +118,42 @@ PHP_LIBXML_API void php_libxml_shutdown(void);
ZEND_TSRMLS_CACHE_EXTERN()
#endif
+/* Other extension may override the global state options, these global options
+ * are copied initially to ctxt->options. Set the options to a known good value.
+ * See libxml2 globals.c and parserInternals.c.
+ * The unique_name argument allows multiple sanitizes and restores within the
+ * same function, even nested is necessary. */
+#define PHP_LIBXML_SANITIZE_GLOBALS(unique_name) \
+ int xml_old_loadsubset_##unique_name = xmlLoadExtDtdDefaultValue; \
+ xmlLoadExtDtdDefaultValue = 0; \
+ int xml_old_validate_##unique_name = xmlDoValidityCheckingDefaultValue; \
+ xmlDoValidityCheckingDefaultValue = 0; \
+ int xml_old_pedantic_##unique_name = xmlPedanticParserDefault(0); \
+ int xml_old_substitute_##unique_name = xmlSubstituteEntitiesDefault(0); \
+ int xml_old_linenrs_##unique_name = xmlLineNumbersDefault(0); \
+ int xml_old_blanks_##unique_name = xmlKeepBlanksDefault(1);
+
+#define PHP_LIBXML_RESTORE_GLOBALS(unique_name) \
+ xmlLoadExtDtdDefaultValue = xml_old_loadsubset_##unique_name; \
+ xmlDoValidityCheckingDefaultValue = xml_old_validate_##unique_name; \
+ (void) xmlPedanticParserDefault(xml_old_pedantic_##unique_name); \
+ (void) xmlSubstituteEntitiesDefault(xml_old_substitute_##unique_name); \
+ (void) xmlLineNumbersDefault(xml_old_linenrs_##unique_name); \
+ (void) xmlKeepBlanksDefault(xml_old_blanks_##unique_name);
+
+/* Alternative for above, working directly on the context and not setting globals.
+ * Generally faster because no locking is involved, and this has the advantage that it sets the options to a known good value. */
+static zend_always_inline void php_libxml_sanitize_parse_ctxt_options(xmlParserCtxtPtr ctxt)
+{
+ ctxt->loadsubset = 0;
+ ctxt->validate = 0;
+ ctxt->pedantic = 0;
+ ctxt->replaceEntities = 0;
+ ctxt->linenumbers = 0;
+ ctxt->keepBlanks = 1;
+ ctxt->options = 0;
+}
+
#else /* HAVE_LIBXML */
#define libxml_module_ptr NULL
#endif
diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c
index 21e1190e681b0..6e0b1e91c4981 100644
--- a/ext/simplexml/simplexml.c
+++ b/ext/simplexml/simplexml.c
@@ -2278,7 +2278,9 @@ PHP_FUNCTION(simplexml_load_file)
RETURN_THROWS();
}
+ PHP_LIBXML_SANITIZE_GLOBALS(read_file);
docp = xmlReadFile(filename, NULL, (int)options);
+ PHP_LIBXML_RESTORE_GLOBALS(read_file);
if (!docp) {
RETURN_FALSE;
@@ -2331,7 +2333,9 @@ PHP_FUNCTION(simplexml_load_string)
RETURN_THROWS();
}
+ PHP_LIBXML_SANITIZE_GLOBALS(read_memory);
docp = xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
+ PHP_LIBXML_RESTORE_GLOBALS(read_memory);
if (!docp) {
RETURN_FALSE;
@@ -2380,7 +2384,9 @@ SXE_METHOD(__construct)
RETURN_THROWS();
}
+ PHP_LIBXML_SANITIZE_GLOBALS(read_file_or_memory);
docp = is_url ? xmlReadFile(data, NULL, (int)options) : xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
+ PHP_LIBXML_RESTORE_GLOBALS(read_file_or_memory);
if (!docp) {
((php_libxml_node_object *)sxe)->document = NULL;
diff --git a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
new file mode 100644
index 0000000000000..2152e0123286a
--- /dev/null
+++ b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
@@ -0,0 +1,36 @@
+--TEST--
+GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+--SKIPIF--
+
+--FILE--
+ %bork;]>";
+
+libxml_use_internal_errors(true);
+zend_test_override_libxml_global_state();
+
+echo "--- String test ---\n";
+simplexml_load_string($xml);
+echo "--- Constructor test ---\n";
+new SimpleXMLElement($xml);
+echo "--- File test ---\n";
+file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
+simplexml_load_file("libxml_global_state_entity_loader_bypass.tmp");
+
+echo "Done\n";
+
+?>
+--CLEAN--
+
+--EXPECT--
+--- String test ---
+--- Constructor test ---
+--- File test ---
+Done
diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c
index e290786f7afc8..ed3495c1266c6 100644
--- a/ext/soap/php_xml.c
+++ b/ext/soap/php_xml.c
@@ -91,6 +91,7 @@ xmlDocPtr soap_xmlParseFile(const char *filename)
if (ctxt) {
zend_bool old;
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
ctxt->keepBlanks = 0;
ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
ctxt->sax->comment = soap_Comment;
@@ -139,6 +140,7 @@ xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size)
if (ctxt) {
zend_bool old;
+ php_libxml_sanitize_parse_ctxt_options(ctxt);
ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
ctxt->sax->comment = soap_Comment;
ctxt->sax->warning = NULL;
diff --git a/ext/xml/compat.c b/ext/xml/compat.c
index dcdd964334c52..3b2a0cdf7fb3b 100644
--- a/ext/xml/compat.c
+++ b/ext/xml/compat.c
@@ -17,6 +17,7 @@
#include "php.h"
#if defined(HAVE_LIBXML) && (defined(HAVE_XML) || defined(HAVE_XMLRPC)) && !defined(HAVE_LIBEXPAT)
#include "expat_compat.h"
+#include "ext/libxml/php_libxml.h"
typedef struct _php_xml_ns {
xmlNsPtr nsptr;
@@ -469,6 +470,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *m
return NULL;
}
+ php_libxml_sanitize_parse_ctxt_options(parser->parser);
xmlCtxtUseOptions(parser->parser, XML_PARSE_OLDSAX);
parser->parser->replaceEntities = 1;
diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c
index d8f8105010c71..77f0c10db3195 100644
--- a/ext/xmlreader/php_xmlreader.c
+++ b/ext/xmlreader/php_xmlreader.c
@@ -284,6 +284,7 @@ static xmlRelaxNGPtr _xmlreader_get_relaxNG(char *source, size_t source_len, siz
return NULL;
}
+ PHP_LIBXML_SANITIZE_GLOBALS(parse);
if (error_func || warn_func) {
xmlRelaxNGSetParserErrors(parser,
(xmlRelaxNGValidityErrorFunc) error_func,
@@ -292,6 +293,7 @@ static xmlRelaxNGPtr _xmlreader_get_relaxNG(char *source, size_t source_len, siz
}
sptr = xmlRelaxNGParse(parser);
xmlRelaxNGFreeParserCtxt(parser);
+ PHP_LIBXML_RESTORE_GLOBALS(parse);
return sptr;
}
@@ -872,7 +874,9 @@ PHP_METHOD(XMLReader, open)
valid_file = _xmlreader_get_valid_file_path(source, resolved_path, MAXPATHLEN );
if (valid_file) {
+ PHP_LIBXML_SANITIZE_GLOBALS(reader_for_file);
reader = xmlReaderForFile(valid_file, encoding, options);
+ PHP_LIBXML_RESTORE_GLOBALS(reader_for_file);
}
if (reader == NULL) {
@@ -945,7 +949,9 @@ PHP_METHOD(XMLReader, setSchema)
intern = Z_XMLREADER_P(id);
if (intern && intern->ptr) {
+ PHP_LIBXML_SANITIZE_GLOBALS(schema);
retval = xmlTextReaderSchemaValidate(intern->ptr, source);
+ PHP_LIBXML_RESTORE_GLOBALS(schema);
if (retval == 0) {
RETURN_TRUE;
@@ -1068,6 +1074,7 @@ PHP_METHOD(XMLReader, XML)
}
uri = (char *) xmlCanonicPath((const xmlChar *) resolved_path);
}
+ PHP_LIBXML_SANITIZE_GLOBALS(text_reader);
reader = xmlNewTextReader(inputbfr, uri);
if (reader != NULL) {
@@ -1086,9 +1093,11 @@ PHP_METHOD(XMLReader, XML)
xmlFree(uri);
}
+ PHP_LIBXML_RESTORE_GLOBALS(text_reader);
return;
}
}
+ PHP_LIBXML_RESTORE_GLOBALS(text_reader);
}
if (uri) {
diff --git a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
new file mode 100644
index 0000000000000..e9ffb04c2bb9d
--- /dev/null
+++ b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+--SKIPIF--
+
+--FILE--
+ %bork;]>";
+
+libxml_use_internal_errors(true);
+zend_test_override_libxml_global_state();
+
+echo "--- String test ---\n";
+$reader = XMLReader::xml($xml);
+$reader->read();
+echo "--- File test ---\n";
+file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
+$reader = XMLReader::open("libxml_global_state_entity_loader_bypass.tmp");
+$reader->read();
+
+echo "Done\n";
+
+?>
+--CLEAN--
+
+--EXPECT--
+--- String test ---
+--- File test ---
+Done
diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
index 9498ba576c6e2..6f17c8fc64254 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -315,7 +315,7 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
xmlDoc *doc = NULL, *newdoc = NULL;
xsltStylesheetPtr sheetp, oldsheetp;
xsl_object *intern;
- int prevSubstValue, prevExtDtdValue, clone_docu = 0;
+ int clone_docu = 0;
xmlNode *nodep = NULL;
zval *cloneDocu, rv;
zend_string *member;
@@ -339,13 +339,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
stylesheet document otherwise the node proxies will be a mess */
newdoc = xmlCopyDoc(doc, 1);
xmlNodeSetBase((xmlNodePtr) newdoc, (xmlChar *)doc->URL);
- prevSubstValue = xmlSubstituteEntitiesDefault(1);
- prevExtDtdValue = xmlLoadExtDtdDefaultValue;
+ PHP_LIBXML_SANITIZE_GLOBALS(parse);
+ xmlSubstituteEntitiesDefault(1);
xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
sheetp = xsltParseStylesheetDoc(newdoc);
- xmlSubstituteEntitiesDefault(prevSubstValue);
- xmlLoadExtDtdDefaultValue = prevExtDtdValue;
+ PHP_LIBXML_RESTORE_GLOBALS(parse);
if (!sheetp) {
xmlFreeDoc(newdoc);
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index 69578e0ad1a55..ca8782c546cf0 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -29,6 +29,11 @@
#include "zend_smart_str.h"
#include "zend_weakrefs.h"
+#ifdef HAVE_LIBXML
+# include
+# include
+#endif
+
ZEND_BEGIN_MODULE_GLOBALS(zend_test)
int observer_enabled;
int observer_show_output;
@@ -270,6 +275,18 @@ static ZEND_FUNCTION(zend_get_current_func_name)
RETURN_STR(function_name);
}
+static ZEND_FUNCTION(zend_test_override_libxml_global_state)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ xmlLoadExtDtdDefaultValue = 1;
+ xmlDoValidityCheckingDefaultValue = 1;
+ (void) xmlPedanticParserDefault(1);
+ (void) xmlSubstituteEntitiesDefault(1);
+ (void) xmlLineNumbersDefault(1);
+ (void) xmlKeepBlanksDefault(0);
+}
+
/* TESTS Z_PARAM_ITERABLE and Z_PARAM_ITERABLE_OR_NULL */
static ZEND_FUNCTION(zend_iterable)
{
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index 812688ac02ff2..fe917dbc8dd93 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -68,6 +68,10 @@ function zend_weakmap_dump(): array {}
function zend_get_current_func_name(): string {}
+#ifdef HAVE_LIBXML
+function zend_test_override_libxml_global_state(): void {}
+#endif
+
}
namespace ZendTestNS {
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index c3a8d98a8c507..73a844583932e 100644
--- a/ext/zend_test/test_arginfo.h
+++ b/ext/zend_test/test_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 1f8834339ebf0d56d2e4ec7f3d27d837f61ba5ef */
+ * Stub hash: 8cfd05fb9f7524837d23f92b185fcd30f964521f */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
@@ -65,6 +65,11 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_current_func_name, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()
+#if defined(HAVE_LIBXML)
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_override_libxml_global_state, 0, 0, IS_VOID, 0)
+ZEND_END_ARG_INFO()
+#endif
+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
@@ -101,6 +106,9 @@ static ZEND_FUNCTION(zend_weakmap_attach);
static ZEND_FUNCTION(zend_weakmap_remove);
static ZEND_FUNCTION(zend_weakmap_dump);
static ZEND_FUNCTION(zend_get_current_func_name);
+#if defined(HAVE_LIBXML)
+static ZEND_FUNCTION(zend_test_override_libxml_global_state);
+#endif
static ZEND_FUNCTION(namespaced_func);
static ZEND_METHOD(_ZendTestClass, is_object);
static ZEND_METHOD(_ZendTestClass, __toString);
@@ -128,6 +136,9 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_weakmap_remove, arginfo_zend_weakmap_remove)
ZEND_FE(zend_weakmap_dump, arginfo_zend_weakmap_dump)
ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name)
+#if defined(HAVE_LIBXML)
+ ZEND_FE(zend_test_override_libxml_global_state, arginfo_zend_test_override_libxml_global_state)
+#endif
ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
ZEND_FE_END
};