| 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 |
| 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 |