postgres.git / summary / log / commit / refs
commit 9d33a5a804db48b254de7a0ad2fde03152f378e3
Author: Michael Paquier <michael@paquier.xyz>
Date: Thu Jun 11 05:29:18 2026 +0000
xml2: Fix crash with namespace nodes in xpath_nodeset()
pgxmlNodeSetToText() passed nodeTab[i]->doc to xmlNodeDump() without
checking the node type, which could cause a crash as a
XML_NAMESPACE_DECL maps to a xmlNs struct. The passed-in code would
then be dereferenced in xmlNodeDump().
This commit switches the code to render XML_NAMESPACE_DECL nodes with
xmlXPathCastNodeToString(), like xpath_table(). Some tests are added,
written by me.
Author: Andrey Chernyy <andrey.cherny@tantorlabs.com>
Co-authored-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/20260611031436.5afde3cb@andrnote
Backpatch-through: 14
contrib/xml2/expected/xml2.out | 8 ++++++++
contrib/xml2/expected/xml2_1.out | 8 ++++++++
contrib/xml2/sql/xml2.sql | 3 +++
contrib/xml2/xpath.c | 23 +++++++++++++++++++----
4 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out
index 1906fcf33e2..9078f15f6b3 100644
--- a/contrib/xml2/expected/xml2.out
+++ b/contrib/xml2/expected/xml2.out
@@ -231,6 +231,14 @@ SELECT xpath_nodeset(article_xml::text, '/article/author|/article/pages',
<result><item><author>test</author></item><item><pages>37</pages></item></result>
(1 row)
+-- namespace node
+SELECT xpath_nodeset('<root xmlns:foo="http://icl.com/saxon"/>',
+ '//namespace::foo');
+ xpath_nodeset
+----------------------
+ http://icl.com/saxon
+(1 row)
+
-- xpath_list()
SELECT xpath_list(article_xml::text, '/article/author|/article/pages')
FROM articles;
diff --git a/contrib/xml2/expected/xml2_1.out b/contrib/xml2/expected/xml2_1.out
index 9a2144d58f5..62e8bd6802a 100644
--- a/contrib/xml2/expected/xml2_1.out
+++ b/contrib/xml2/expected/xml2_1.out
@@ -175,6 +175,14 @@ SELECT xpath_nodeset(article_xml::text, '/article/author|/article/pages',
<result><item><author>test</author></item><item><pages>37</pages></item></result>
(1 row)
+-- namespace node
+SELECT xpath_nodeset('<root xmlns:foo="http://icl.com/saxon"/>',
+ '//namespace::foo');
+ xpath_nodeset
+----------------------
+ http://icl.com/saxon
+(1 row)
+
-- xpath_list()
SELECT xpath_list(article_xml::text, '/article/author|/article/pages')
FROM articles;
diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql
index 510d18a3679..145c487cbde 100644
--- a/contrib/xml2/sql/xml2.sql
+++ b/contrib/xml2/sql/xml2.sql
@@ -132,6 +132,9 @@ SELECT xpath_nodeset(article_xml::text, '/article/author|/article/pages',
SELECT xpath_nodeset(article_xml::text, '/article/author|/article/pages',
'result', 'item')
FROM articles;
+-- namespace node
+SELECT xpath_nodeset('<root xmlns:foo="http://icl.com/saxon"/>',
+ '//namespace::foo');
-- xpath_list()
SELECT xpath_list(article_xml::text, '/article/author|/article/pages')
diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c
index 283bb51178d..391e39827ce 100644
--- a/contrib/xml2/xpath.c
+++ b/contrib/xml2/xpath.c
@@ -188,16 +188,31 @@ pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
}
else
{
+ xmlNodePtr node = nodeset->nodeTab[i];
+
if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
{
xmlBufferWriteChar(buf, "<");
xmlBufferWriteCHAR(buf, septagname);
xmlBufferWriteChar(buf, ">");
}
- xmlNodeDump(buf,
- nodeset->nodeTab[i]->doc,
- nodeset->nodeTab[i],
- 1, 0);
+
+ /*
+ * XML_NAMESPACE_DECL nodes are xmlNs structs, that cannot
+ * be processed by xmlNodeDump().
+ */
+ if (node->type == XML_NAMESPACE_DECL)
+ {
+ str = xmlXPathCastNodeToString(node);
+ if (str == NULL || pg_xml_error_occurred(xmlerrcxt))
+ xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+ "could not allocate node text");
+ xmlBufferWriteCHAR(buf, str);
+ xmlFree(str);
+ str = NULL;
+ }
+ else
+ xmlNodeDump(buf, node->doc, node, 1, 0);
if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
{