Uncancellable infinite loop in xpath() with entity references

From: Jim Jones <jim(dot)jones(at)uni-muenster(dot)de>
To: Postgres hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Uncancellable infinite loop in xpath() with entity references
Date: 2026-10-29 11:11:37
Message-ID: d3270e9f-4f10-4d04-8209-87e513801b05@uni-muenster.de
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi

While investigating an interrupt issue with libxml2 queries I found a
query that cannot be cancelled and runs indefinitely. While inside
libxml2, a backend ignores cancel, statement_timeout and
pg_terminate_backend(), so the only way I found was a kill -9 and a
cluster restart.

psql (20devel)
Type "help" for help.

postgres=# SELECT xpath('/r/text()/following::comment()',
'<!DOCTYPE r [<!ENTITY e "y">]><r>u&e;</r>');
^CCancel request sent
^CCancel request sent
^CCancel request sent

Most likely a bug in libxml2 (xmlXPathNextFollowing) -- I will
investigate that and report.

It only happens because xpath_internal() and XmlTableSetDocument() parse
without XML_PARSE_NOENT, so entity references stay in the tree.
xml_parse() does use it:

...
options = XML_PARSE_NOENT | XML_PARSE_DTDATTR
| (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS);

As a result,xpath() evaluates a different document than the rest of the
xml code sees:

psql (20devel)
Type "help" for help.

postgres=# select xmlserialize(document '<!DOCTYPE r [<!ENTITY e
"<b>z</b>">]><r>&e;&e;</r>'::xml as text indent);
xmlserialize
------------------------
<!DOCTYPE r [ +
<!ENTITY e "<b>z</b>">+
]> +
<r> +
<b>z</b> +
<b>z</b> +
</r>
(1 row)

postgres=# select xpath('count(//b)', '<!DOCTYPE r [<!ENTITY e
"<b>z</b>">]><r>&e;&e;</r>');
xpath
-------
{0}
(1 row)

postgres=# with t (x) as (
select xmlserialize(document '<!DOCTYPE r [<!ENTITY e
"<b>z</b>">]><r>&e;&e;</r>'::xml as text indent)
)
select xpath('count(//b)', x::xml) from t;
xpath
-------
{2}
(1 row)

BTW, the deprecated xml2 parses with XML_PARSE_NOENT .

The attached patch adds XML_PARSE_NOENT to both calls (+ some tests).
That fixes the endless loop and makes xpath(), xmlexists() and XMLTABLE
see entity content.

psql (20devel)
Type "help" for help.

postgres=# SELECT xpath('/r/text()/following::comment()',
'<!DOCTYPE r [<!ENTITY e "y">]><r>u&e;</r>');
xpath
-------
{}
(1 row)

postgres=# select xpath('count(//b)', '<!DOCTYPE r [<!ENTITY e
"<b>z</b>">]><r>&e;&e;</r>');
xpath
-------
{2}
(1 row)

This changes results for documents that use entity references, but it's
arguably an improvement, e.g.

-- master

postgres=# select xpath('/r/text()', '<!DOCTYPE r [<!ENTITY e
"y">]><r>u&e;</r>');
xpath
-------
{u}
(1 row)

-- patched

postgres=# select xpath('/r/text()', '<!DOCTYPE r [<!ENTITY e
"y">]><r>u&e;</r>');
xpath
-------
{uy}
(1 row)

Documents without entity references are not affected.

WDYT?

Best, Jim

Attachment Content-Type Size
v1-0001-Expand-entity-references-in-xpath-xmlexists-and-X.patch text/x-patch 4.2 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Roman Khapov 2026-10-29 11:27:45 Check the fd number against FD_SETSIZE in PQsocketPoll
Previous Message Aleksander Alekseev 2026-10-29 11:03:55 [PATCH] dynahash.c refactoring