Thread
-
[PATCH] Fix libxml leaks in contrib/xml2 XPath functions
Andrey Chernyy <andrey.cherny@tantorlabs.com> — 2026-05-31T22:01:24Z
Hi, While reviewing contrib/xml2 I found two successful-path libxml leaks in XPath functions. The attached series is: 0001 Fix libxml string leak in contrib/xml2 xpath_list 0002 Fix libxml leaks in contrib/xml2 xpath_table In xpath_list(), the plain separator path passes the result of xmlXPathCastNodeToString() directly to xmlBufferWriteCHAR(). xmlXPathCastNodeToString() returns a libxml-allocated xmlChar * that has to be released with xmlFree(), while xmlBufferWriteCHAR() copies the string rather than taking ownership. In xpath_table(), xmlXPathCompiledEval() returns an xmlXPathObjectPtr that has to be released with xmlXPathFreeObject(). The function also stores libxml-allocated xmlChar strings in the values array before calling BuildTupleFromCStrings(). BuildTupleFromCStrings() consumes those strings by converting/copying them into the result tuple, so the temporary libxml strings can be released after the tuple is built. The second patch frees the XPath result object after each evaluation, frees per-column string values after BuildTupleFromCStrings() has consumed them, and tracks the current libxml allocations across the existing PG_TRY block so they are also released on error. The attached manual repro scripts exercise the two paths separately. They are Linux-specific because they sample VmRSS from /proc/<backend-pid>/status using pg_read_file(), so they should be run as a superuser or a role allowed to read server files. On unpatched origin/master, selected NOTICE lines from the repro scripts show steady backend RSS growth: postgres=# \i ./xml2-xpath-list-leak-repro.sql psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=1, total_kb=38616, diff_kb=17220 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=2, total_kb=40732, diff_kb=2104 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=3, total_kb=42720, diff_kb=1988 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=4, total_kb=44644, diff_kb=1924 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=5, total_kb=46440, diff_kb=1796 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=6, total_kb=47968, diff_kb=1528 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=7, total_kb=50892, diff_kb=2924 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=8, total_kb=52712, diff_kb=1820 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=9, total_kb=54276, diff_kb=1564 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=10, total_kb=55328, diff_kb=1052 postgres=# \i ./xml2-xpath-table-leak-repro.sql psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=1, total_kb=26452, diff_kb=5136 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=2, total_kb=27772, diff_kb=1312 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=3, total_kb=29068, diff_kb=1296 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=4, total_kb=30368, diff_kb=1300 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=5, total_kb=31668, diff_kb=1300 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=6, total_kb=32968, diff_kb=1300 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=7, total_kb=34260, diff_kb=1292 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=8, total_kb=35568, diff_kb=1308 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=9, total_kb=36876, diff_kb=1308 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=10, total_kb=38168, diff_kb=1292 With both patches applied, the same scripts plateau after the initial allocations: postgres=# \i ./xml2-xpath-list-leak-repro.sql psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=1, total_kb=24480, diff_kb=3020 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=2, total_kb=24532, diff_kb=44 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=3, total_kb=24580, diff_kb=48 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=4, total_kb=24580, diff_kb=0 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=5, total_kb=24624, diff_kb=44 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=6, total_kb=24580, diff_kb=-44 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=7, total_kb=24580, diff_kb=0 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=8, total_kb=24580, diff_kb=0 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=9, total_kb=24580, diff_kb=0 psql:xml2-xpath-list-leak-repro.sql:38: NOTICE: xpath_list i=10, total_kb=24580, diff_kb=0 postgres=# \i ./xml2-xpath-table-leak-repro.sql psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=1, total_kb=26572, diff_kb=188 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=2, total_kb=26600, diff_kb=28 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=3, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=4, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=5, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=6, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=7, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=8, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=9, total_kb=26600, diff_kb=0 psql:xml2-xpath-table-leak-repro.sql:40: NOTICE: xpath_table i=10, total_kb=26600, diff_kb=0 contrib/xml2 regression tests still pass: make -C contrib/xml2 check # All 1 tests passed. I also checked that the two patches apply cleanly to current origin/master with git am. This is related to, but not fixed by, the recent xml2/libxml error-handling work from BUG #18943 / commit 732061150b0. That patch improved cleanup and OOM handling around libxml calls, while these cases are successful execution path leaks. These are long-standing leaks and look like candidates for back-patching to all supported branches. -- Andrey Chernyy