[PATCH] xml2: Fix stylesheet document leak in xslt_process()

Started by Andrey Chernyy19 days ago2 messageshackers
Jump to latest
#1Andrey Chernyy
andrey.cherny@tantorlabs.com

Hi,

While following up on the recent xml2 XPath leak fixes at
/messages/by-id/20260601010124.5edf9a20@andrnote, I noticed the same
class of libxml/libxslt ownership issue in xslt_process().

xslt_process() parses the stylesheet argument with xmlReadMemory(), then
passes the resulting xmlDoc to xsltParseStylesheetDoc(). On failure,
libxslt leaves that document owned by the caller, as can be seen from
its own xsltParseStylesheetFile() wrapper. Postgres currently cannot
release it in the error cleanup path because ssdoc is scoped inside the
PG_TRY block.

The attached patch keeps ssdoc visible to the cleanup path, clears it
once ownership has been transferred to the stylesheet, and frees it in
PG_CATCH if parsing failed before that transfer completed.

I also attached a manual repro script. It repeatedly calls
xslt_process() with a large XML document that is not a stylesheet,
catches the ERROR in one backend, and samples VmRSS via /proc.

On my machine, without the patch, the same backend kept growing after
each failed parse:

NOTICE: xslt_process i=1, failures=1, total_kb=34668, diff_kb=12992
NOTICE: xslt_process i=2, failures=1, total_kb=44428, diff_kb=9760
NOTICE: xslt_process i=3, failures=1, total_kb=54120, diff_kb=9692
NOTICE: xslt_process i=4, failures=1, total_kb=63808, diff_kb=9688
NOTICE: xslt_process i=5, failures=1, total_kb=73496, diff_kb=9688
NOTICE: xslt_process i=6, failures=1, total_kb=83188, diff_kb=9692
NOTICE: xslt_process i=7, failures=1, total_kb=92876, diff_kb=9688
NOTICE: xslt_process i=8, failures=1, total_kb=102564, diff_kb=9688
NOTICE: xslt_process i=9, failures=1, total_kb=112256, diff_kb=9692
NOTICE: xslt_process i=10, failures=1, total_kb=121944, diff_kb=9688

With the patch, it plateaued after the initial warmup:

NOTICE: xslt_process i=1, failures=1, total_kb=23228, diff_kb=1596
NOTICE: xslt_process i=2, failures=1, total_kb=23888, diff_kb=660
NOTICE: xslt_process i=3, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=4, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=5, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=6, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=7, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=8, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=9, failures=1, total_kb=23888, diff_kb=0
NOTICE: xslt_process i=10, failures=1, total_kb=23888, diff_kb=0

--
Andrey Chernyy

Attachments:

xml2-xslt-process-leak-repro.sqlapplication/sqlDownload
0001-xml2-Fix-stylesheet-document-leak-in-xslt_process.patchtext/x-patchDownload+9-3
#2Michael Paquier
michael@paquier.xyz
In reply to: Andrey Chernyy (#1)
Re: [PATCH] xml2: Fix stylesheet document leak in xslt_process()

On Fri, Jun 05, 2026 at 02:46:42AM +0300, Andrey Chernyy wrote:

xslt_process() parses the stylesheet argument with xmlReadMemory(), then
passes the resulting xmlDoc to xsltParseStylesheetDoc(). On failure,
libxslt leaves that document owned by the caller, as can be seen from
its own xsltParseStylesheetFile() wrapper. Postgres currently cannot
release it in the error cleanup path because ssdoc is scoped inside the
PG_TRY block.

In libxslt/xslt.c, the top comment of xsltParseStylesheetDoc() says:
"the doc is automatically freed when the stylesheet is closed."

Reading the code, I can confirm that xsltFreeStylesheet() does a bunch
of stuff, and that it has the idea to call xmlFreeDoc() once at the
end.

Will address this one as well. Thanks.
--
Michael