Thread: BUG #18617: PostgreSQL Server Subprocess Crashes by the XPATH Function Expression with Crafted Arguments

The following bug has been logged on the website:

Bug reference:      18617
Logged by:          Jingzhou Fu
Email address:      fuboat@outlook.com
PostgreSQL version: 17rc1
Operating system:   Ubuntu 20.04 with docker image 'postgres:17rc1'
Description:

PostgreSQL server 17rc1 subprocess crashes by the XPATH function expression
with crafted arguments. The Main process is not affected.

PoC:
```
SELECT XPATH(REPEAT('(', 100000), '<root/>');
```

Client Output:
```
psql (17rc1 (Debian 17~rc1-1.pgdg120+1))
Type "help" for help.
postgres=# SELECT XPATH(REPEAT('(', 100000), '<root/>');
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
```

GDB Backtrace (It seems to be a stack overflow):
```
#0  0x00007a66b81a74b2 in ?? () from
target:/lib/x86_64-linux-gnu/libxml2.so.2
#1  0x00007a66b81a86d6 in ?? () from
target:/lib/x86_64-linux-gnu/libxml2.so.2
#2  0x00007a66b81a908d in ?? () from
target:/lib/x86_64-linux-gnu/libxml2.so.2
...
#120767 0x00007a66b81a908d in ?? () from
target:/lib/x86_64-linux-gnu/libxml2.so.2
#120768 0x00007a66b81af062 in xmlXPathCompile () from
target:/lib/x86_64-linux-gnu/libxml2.so.2
#120769 0x00005a0ebb12c131 in ?? ()
#120770 0x00005a0ebb12cd4e in xpath ()
#120771 0x00005a0ebae4c67a in ?? ()
#120772 0x00005a0ebae7f6b3 in ?? ()
#120773 0x00005a0ebae50145 in standard_ExecutorRun ()
#120774 0x00005a0ebae5f2fd in fmgr_sql ()
#120775 0x00005a0ebae4c67a in ?? ()
#120776 0x00005a0ebaf46efc in evaluate_expr ()
#120777 0x00005a0ebaf47140 in ?? ()
#120778 0x00005a0ebaf480fd in ?? ()
#120779 0x00005a0ebaebe58b in expression_tree_mutator_impl ()
#120780 0x00005a0ebaebe495 in expression_tree_mutator_impl ()
#120781 0x00005a0ebaf48fc7 in eval_const_expressions ()
#120782 0x00005a0ebaf2c953 in ?? ()
#120783 0x00005a0ebaf34b02 in subquery_planner ()
#120784 0x00005a0ebaf35459 in standard_planner ()
#120785 0x00005a0ebb001440 in pg_plan_query ()
#120786 0x00005a0ebb001532 in pg_plan_queries ()
#120787 0x00005a0ebb001806 in ?? ()
#120788 0x00005a0ebb0033ee in PostgresMain ()
#120789 0x00005a0ebaffde7f in BackendMain ()
#120790 0x00005a0ebaf6bf2a in postmaster_child_launch ()
#120791 0x00005a0ebaf6fa39 in ?? ()
#120792 0x00005a0ebaf71958 in PostmasterMain ()
#120793 0x00005a0ebac96476 in main ()
```


On 2024-09-13 14:31 +0200, PG Bug reporting form wrote:
> The following bug has been logged on the website:
> 
> Bug reference:      18617
> Logged by:          Jingzhou Fu
> Email address:      fuboat@outlook.com
> PostgreSQL version: 17rc1
> Operating system:   Ubuntu 20.04 with docker image 'postgres:17rc1'
> Description:        
> 
> PostgreSQL server 17rc1 subprocess crashes by the XPATH function expression
> with crafted arguments. The Main process is not affected.
> 
> PoC:
> ```
> SELECT XPATH(REPEAT('(', 100000), '<root/>');
> ```

This looks a lot like https://gitlab.gnome.org/GNOME/libxml2/-/issues/137

What is your libxml2 version?

> Client Output:
> ```
> psql (17rc1 (Debian 17~rc1-1.pgdg120+1))
> Type "help" for help.
> postgres=# SELECT XPATH(REPEAT('(', 100000), '<root/>');
> server closed the connection unexpectedly
>         This probably means the server terminated abnormally
>         before or while processing the request.
> The connection to the server was lost. Attempting reset: Failed.
> ```
> 
> GDB Backtrace (It seems to be a stack overflow):
> ```
> #0  0x00007a66b81a74b2 in ?? () from
> target:/lib/x86_64-linux-gnu/libxml2.so.2
> #1  0x00007a66b81a86d6 in ?? () from
> target:/lib/x86_64-linux-gnu/libxml2.so.2
> #2  0x00007a66b81a908d in ?? () from
> target:/lib/x86_64-linux-gnu/libxml2.so.2
> ...
> #120767 0x00007a66b81a908d in ?? () from
> target:/lib/x86_64-linux-gnu/libxml2.so.2
> #120768 0x00007a66b81af062 in xmlXPathCompile () from
> target:/lib/x86_64-linux-gnu/libxml2.so.2
> #120769 0x00005a0ebb12c131 in ?? ()
> #120770 0x00005a0ebb12cd4e in xpath ()
> #120771 0x00005a0ebae4c67a in ?? ()
> #120772 0x00005a0ebae7f6b3 in ?? ()
> #120773 0x00005a0ebae50145 in standard_ExecutorRun ()
> #120774 0x00005a0ebae5f2fd in fmgr_sql ()
> #120775 0x00005a0ebae4c67a in ?? ()
> #120776 0x00005a0ebaf46efc in evaluate_expr ()
> #120777 0x00005a0ebaf47140 in ?? ()
> #120778 0x00005a0ebaf480fd in ?? ()
> #120779 0x00005a0ebaebe58b in expression_tree_mutator_impl ()
> #120780 0x00005a0ebaebe495 in expression_tree_mutator_impl ()
> #120781 0x00005a0ebaf48fc7 in eval_const_expressions ()
> #120782 0x00005a0ebaf2c953 in ?? ()
> #120783 0x00005a0ebaf34b02 in subquery_planner ()
> #120784 0x00005a0ebaf35459 in standard_planner ()
> #120785 0x00005a0ebb001440 in pg_plan_query ()
> #120786 0x00005a0ebb001532 in pg_plan_queries ()
> #120787 0x00005a0ebb001806 in ?? ()
> #120788 0x00005a0ebb0033ee in PostgresMain ()
> #120789 0x00005a0ebaffde7f in BackendMain ()
> #120790 0x00005a0ebaf6bf2a in postmaster_child_launch ()
> #120791 0x00005a0ebaf6fa39 in ?? ()
> #120792 0x00005a0ebaf71958 in PostmasterMain ()
> #120793 0x00005a0ebac96476 in main ()
> ```

I can reproduce it with libxml2 2.13.3 on 70d1c664f4:

    Program received signal SIGSEGV, Segmentation fault.
    0x00007a19bf209d62 in ?? () from /usr/lib/libxml2.so.2
    #0  0x00007a19bf209d62 in ?? () from /usr/lib/libxml2.so.2
    #1  0x00007a19bf208763 in ?? () from /usr/lib/libxml2.so.2
    #2  0x00007a19bf209408 in ?? () from /usr/lib/libxml2.so.2
    #3  0x00007a19bf209d96 in ?? () from /usr/lib/libxml2.so.2
    [...snip...]
    #104680 0x00007a19bf208763 in ?? () from /usr/lib/libxml2.so.2
    #104681 0x00007a19bf209408 in ?? () from /usr/lib/libxml2.so.2
    #104682 0x00007a19bf209d96 in ?? () from /usr/lib/libxml2.so.2
    #104683 0x00007a19bf20d655 in xmlXPathCtxtCompile () from /usr/lib/libxml2.so.2
    #104684 0x00005fcff7ca11fc in xpath_internal ()
    #104685 0x00005fcff7ca1d5d in xpath ()
    #104686 0x00005fcff79cc33e in ExecInterpExpr ()
    #104687 0x00005fcff79fe8f0 in ExecResult ()
    #104688 0x00005fcff79cfbda in standard_ExecutorRun ()
    #104689 0x00005fcff79df013 in fmgr_sql ()
    #104690 0x00005fcff79cc33e in ExecInterpExpr ()
    #104691 0x00005fcff7abd798 in evaluate_expr ()
    #104692 0x00005fcff7abd991 in simplify_function ()
    #104693 0x00005fcff7abe8ed in eval_const_expressions_mutator ()
    #104694 0x00005fcff7a35b69 in expression_tree_mutator_impl ()
    #104695 0x00005fcff7a36262 in expression_tree_mutator_impl ()
    #104696 0x00005fcff7abf836 in eval_const_expressions ()
    #104697 0x00005fcff7aa38b1 in preprocess_expression ()
    #104698 0x00005fcff7aab394 in subquery_planner ()
    #104699 0x00005fcff7aabfbf in standard_planner ()
    #104700 0x00005fcff7b7660a in pg_plan_query ()
    #104701 0x00005fcff7b76713 in pg_plan_queries ()
    #104702 0x00005fcff7b769e4 in exec_simple_query ()
    #104703 0x00005fcff7b78791 in PostgresMain ()
    #104704 0x00005fcff7b730df in BackendMain ()
    #104705 0x00005fcff7ae2edb in postmaster_child_launch ()
    #104706 0x00005fcff7ae61c1 in ServerLoop.isra.0 ()
    #104707 0x00005fcff7ae7d6d in PostmasterMain ()
    #104708 0x00005fcff78200c3 in main ()

-- 
Erik



PG Bug reporting form <noreply@postgresql.org> writes:
> SELECT XPATH(REPEAT('(', 100000), '<root/>');

> GDB Backtrace (It seems to be a stack overflow):

Yeah, with debug symbols it looks like

(gdb) bt
#0  0x00007f14666a177d in xmlXPathCompMultiplicativeExpr (ctxt=0x2b79ab0) at ../xpath.c:10864
#1  0x00007f14666a1ee4 in xmlXPathCompAdditiveExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#2  xmlXPathCompRelationalExpr (ctxt=0x2b79ab0) at ../xpath.c:10943
#3  xmlXPathCompEqualityExpr (ctxt=0x2b79ab0) at ../xpath.c:10985
#4  xmlXPathCompAndExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#5  xmlXPathCompileExpr (ctxt=0x2b79ab0, sort=1) at ../xpath.c:11042
#6  0x00007f14666a071a in xmlXPathCompPrimaryExpr (ctxt=<optimized out>) at ../xpath.c:10528
#7  xmlXPathCompFilterExpr (ctxt=<optimized out>) at ../xpath.c:10562
#8  xmlXPathCompPathExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10769
#9  0x00007f14666a15b6 in xmlXPathCompUnionExpr (ctxt=0x2b79ab0) at ../xpath.c:10839
#10 xmlXPathCompUnaryExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10839
#11 0x00007f14666a1782 in xmlXPathCompMultiplicativeExpr (ctxt=0x2b79ab0) at ../xpath.c:10864
#12 0x00007f14666a1ee4 in xmlXPathCompAdditiveExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#13 xmlXPathCompRelationalExpr (ctxt=0x2b79ab0) at ../xpath.c:10943
#14 xmlXPathCompEqualityExpr (ctxt=0x2b79ab0) at ../xpath.c:10985
#15 xmlXPathCompAndExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#16 xmlXPathCompileExpr (ctxt=0x2b79ab0, sort=1) at ../xpath.c:11042
#17 0x00007f14666a071a in xmlXPathCompPrimaryExpr (ctxt=<optimized out>) at ../xpath.c:10528
#18 xmlXPathCompFilterExpr (ctxt=<optimized out>) at ../xpath.c:10562
#19 xmlXPathCompPathExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10769
#20 0x00007f14666a15b6 in xmlXPathCompUnionExpr (ctxt=0x2b79ab0) at ../xpath.c:10839
#21 xmlXPathCompUnaryExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10839
#22 0x00007f14666a1782 in xmlXPathCompMultiplicativeExpr (ctxt=0x2b79ab0) at ../xpath.c:10864
#23 0x00007f14666a1ee4 in xmlXPathCompAdditiveExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#24 xmlXPathCompRelationalExpr (ctxt=0x2b79ab0) at ../xpath.c:10943
#25 xmlXPathCompEqualityExpr (ctxt=0x2b79ab0) at ../xpath.c:10985
#26 xmlXPathCompAndExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
#27 xmlXPathCompileExpr (ctxt=0x2b79ab0, sort=1) at ../xpath.c:11042
#28 0x00007f14666a071a in xmlXPathCompPrimaryExpr (ctxt=<optimized out>) at ../xpath.c:10528
#29 xmlXPathCompFilterExpr (ctxt=<optimized out>) at ../xpath.c:10562
#30 xmlXPathCompPathExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10769
#31 0x00007f14666a15b6 in xmlXPathCompUnionExpr (ctxt=0x2b79ab0) at ../xpath.c:10839
#32 xmlXPathCompUnaryExpr (ctxt=ctxt@entry=0x2b79ab0) at ../xpath.c:10839
#33 0x00007f14666a1782 in xmlXPathCompMultiplicativeExpr (ctxt=0x2b79ab0) at ../xpath.c:10864
#34 0x00007f14666a1ee4 in xmlXPathCompAdditiveExpr (ctxt=0x2b79ab0) at ../xpath.c:11016
... etc etc...

Fundamentally, this is a libxml2 bug that we can't do much about.
There are various hard-wired limits on document complexity in libxml2,
but they don't seem to be trapping this particular case, and we have
no ability to adjust them anyway.  You might try filing a bug with
the libxml2 authors.

            regards, tom lane



Erik Wienhold <ewie@ewie.name> writes:
> On 2024-09-13 14:31 +0200, PG Bug reporting form wrote:
>> SELECT XPATH(REPEAT('(', 100000), '<root/>');

> This looks a lot like https://gitlab.gnome.org/GNOME/libxml2/-/issues/137

Interesting.

> What is your libxml2 version?

It crashes with all the libxml2 versions I have at hand, the newest
being 2.13.3 from MacPorts --- which seems to be the latest per
https://download.gnome.org/sources/libxml2/

I wonder what became of the alleged fix.

            regards, tom lane



I wrote:
> Erik Wienhold <ewie@ewie.name> writes:
>> This looks a lot like https://gitlab.gnome.org/GNOME/libxml2/-/issues/137

> I wonder what became of the alleged fix.

I filed a new report at

https://gitlab.gnome.org/GNOME/libxml2/-/issues/799

            regards, tom lane



I wrote:
> I filed a new report at
> https://gitlab.gnome.org/GNOME/libxml2/-/issues/799

Based on Nick Wellnhofer's response there, I've experimented with the
attached WIP patch, and it does seem to prevent the problem as long as
you have a non-ancient libxml2.  This is only WIP because there are
other xmlXPathCompile calls we'd have to fix.

Sadly, still-popular distros like RHEL8 have "ancient" libxml2
versions, but that means they're exposed to the original bug not
only this variant.  It seems to me to be worth masking the bug
where we can, though.

Nick also suggested that we not bother with a separate xmlXPathCompile
call if we're just going to throw away the compiled expression after
one use.  Perhaps that's good cleanup, not sure.  I don't know if
anyone has serious ambitions of re-using the compiled XPath
expressions.

            regards, tom lane

diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 1a07876cd5..37e0cabe60 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4448,7 +4448,7 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
             }
         }

-        xpathcomp = xmlXPathCompile(xpath_expr);
+        xpathcomp = xmlXPathCtxtCompile(xpathctx, xpath_expr);
         if (xpathcomp == NULL || xmlerrcxt->err_occurred)
             xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
                         "invalid XPath expression");

I wrote:
> Based on Nick Wellnhofer's response there, I've experimented with the
> attached WIP patch, and it does seem to prevent the problem as long as
> you have a non-ancient libxml2.  This is only WIP because there are
> other xmlXPathCompile calls we'd have to fix.

Here's a fleshed-out patch.  I first thought that the XmlTableRoutine
code might need significant surgery, but it turns out not to be a
problem as long as we're willing to assume that XmlTableSetDocument
is called before XmlTableSetRowFilter or XmlTableSetColumnFilter.
Since the sole caller does it like that, this doesn't seem too
onerous.  I put in Asserts to check that, though.

> Nick also suggested that we not bother with a separate xmlXPathCompile
> call if we're just going to throw away the compiled expression after
> one use.  Perhaps that's good cleanup, not sure.  I don't know if
> anyone has serious ambitions of re-using the compiled XPath
> expressions.

I looked at this but realized that it'd cause a user-visible change
in error messages.  For instance, in xpath_internal we have

        xpathcomp = xmlXPathCtxtCompile(xpathctx, xpath_expr);
        if (xpathcomp == NULL || xmlerrcxt->err_occurred)
            xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
                        "invalid XPath expression");

        xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
        if (xpathobj == NULL || xmlerrcxt->err_occurred)
            xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
                        "could not create XPath object");

So syntax errors in the XPath expression draw "invalid XPath
expression", but if we merged these into one they'd draw
"could not create XPath object", or at least the same message
as execution-time errors.  That seems strictly worse for users,
and even if it were OK it's not the sort of thing I'd like to
change in minor releases.  So I think we're best off just doing
the minimum change s/xmlXPathCompile/xmlXPathCtxtCompile/.

I would have liked to include a regression test case showing
that the stack overflow is prevented, but I think we can't
since it would fail with pre-2.9.11 libxml2.

BTW, surely ERRCODE_INTERNAL_ERROR is the wrong errcode to use
here?  But that's a matter for a different patch.

            regards, tom lane

diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c
index f94b622d92..0fdf735faf 100644
--- a/contrib/xml2/xpath.c
+++ b/contrib/xml2/xpath.c
@@ -386,7 +386,7 @@ pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace)
             workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree);

             /* compile the path */
-            comppath = xmlXPathCompile(xpath);
+            comppath = xmlXPathCtxtCompile(workspace->ctxt, xpath);
             if (comppath == NULL)
                 xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
                             "XPath Syntax Error");
@@ -649,7 +649,7 @@ xpath_table(PG_FUNCTION_ARGS)
                         ctxt->node = xmlDocGetRootElement(doctree);

                         /* compile the path */
-                        comppath = xmlXPathCompile(xpaths[j]);
+                        comppath = xmlXPathCtxtCompile(ctxt, xpaths[j]);
                         if (comppath == NULL)
                             xml_ereport(xmlerrcxt, ERROR,
                                         ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 1a07876cd5..41b1a5c6b0 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -4448,7 +4448,13 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
             }
         }

-        xpathcomp = xmlXPathCompile(xpath_expr);
+        /*
+         * Note: here and elsewhere, be careful to use xmlXPathCtxtCompile not
+         * xmlXPathCompile.  In libxml2 2.13.3 and older, the latter function
+         * fails to defend itself against recursion-to-stack-overflow.  See
+         * https://gitlab.gnome.org/GNOME/libxml2/-/issues/799
+         */
+        xpathcomp = xmlXPathCtxtCompile(xpathctx, xpath_expr);
         if (xpathcomp == NULL || xmlerrcxt->err_occurred)
             xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
                         "invalid XPath expression");
@@ -4819,7 +4825,10 @@ XmlTableSetRowFilter(TableFuncScanState *state, const char *path)

     xstr = pg_xmlCharStrndup(path, strlen(path));

-    xtCxt->xpathcomp = xmlXPathCompile(xstr);
+    /* We require XmlTableSetDocument to have been done already */
+    Assert(xtCxt->xpathcxt != NULL);
+
+    xtCxt->xpathcomp = xmlXPathCtxtCompile(xtCxt->xpathcxt, xstr);
     if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
         xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_SYNTAX_ERROR,
                     "invalid XPath expression");
@@ -4850,7 +4859,10 @@ XmlTableSetColumnFilter(TableFuncScanState *state, const char *path, int colnum)

     xstr = pg_xmlCharStrndup(path, strlen(path));

-    xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr);
+    /* We require XmlTableSetDocument to have been done already */
+    Assert(xtCxt->xpathcxt != NULL);
+
+    xtCxt->xpathscomp[colnum] = xmlXPathCtxtCompile(xtCxt->xpathcxt, xstr);
     if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
         xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_DATA_EXCEPTION,
                     "invalid XPath expression");