Vulnerability details
In xsltutils.c:
int
xsltSetSourceNodeFlags(xsltTransformContextPtr ctxt, xmlNodePtr node,
int flags) {
if (node->doc == ctxt->initialContextDoc)
ctxt->sourceDocDirty = 1;
switch (node->type) {
case XML_DOCUMENT_NODE:
case XML_HTML_DOCUMENT_NODE:
((xmlDocPtr) node)->properties |= flags << 27;
return 0;
case XML_ATTRIBUTE_NODE:
((xmlAttrPtr) node)->atype |= flags << 27; / *** 1 *** /
return 0;
case XML_ELEMENT_NODE:
case XML_TEXT_NODE:
case XML_CDATA_SECTION_NODE:
case XML_PI_NODE:
case XML_COMMENT_NODE:
node->extra |= flags << 12;
return 0;
default:
return -1;
}
}
In tree.c:
void
xmlFreeProp(xmlAttrPtr cur) {
xmlDictPtr dict = NULL;
if (cur == NULL) return;
if (cur->doc != NULL) dict = cur->doc->dict;
if ((xmlRegisterCallbacks) && (xmlDeregisterNodeDefaultValue))
xmlDeregisterNodeDefaultValue((xmlNodePtr)cur);
/* Check for ID removal -> leading to invalid references ! */
if ((cur->doc != NULL) && (cur->atype == XML_ATTRIBUTE_ID)) { // *** 2 ***
xmlRemoveID(cur->doc, cur);
}
if (cur->children != NULL) xmlFreeNodeList(cur->children);
DICT_FREE(cur->name)
xmlFree(cur);
}
In variables.c:
void
xsltReleaseRVT(xsltTransformContextPtr ctxt, xmlDocPtr RVT)
{
...
if (RVT->children != NULL) {
xmlFreeNodeList(RVT->children); // *** 3 ***
RVT->children = NULL;
RVT->last = NULL;
}
if (RVT->ids != NULL) {
xmlFreeIDTable((xmlIDTablePtr) RVT->ids); // *** 4 ***
RVT->ids = NULL;
}
...
In valid.c:
static void
xmlFreeID(xmlIDPtr id) {
xmlDictPtr dict = NULL;
if (id == NULL) return;
if (id->doc != NULL)
dict = id->doc->dict;
if (id->value != NULL)
DICT_FREE(id->value)
if (id->name != NULL)
DICT_FREE(id->name)
if (id->attr != NULL) {
id->attr->id = NULL; // *** 5 ***
id->attr->atype = 0;
}
xmlFree(id);
}
In xsltutils.c, the xsltSetSourceNodeFlags function may set extra bits on the atype field of an attribute node[1]. This function is called by xsltGenerateIdFunction (which implements the generate-id() XSLT function) and xsltInitCtxtKey (related to the key() XSLT function).
libxml2 treats the atype field as an xmlAttributeType enum and uses it directly in comparisons, making this extra bit setting problematic. Specifically, xmlFreeProp in tree.c checks if an attribute's type is XML_ATTRIBUTE_ID[2]; if so, it removes the element reference from the document's ID table to prevent the reference from becoming dangling. However, xmlRemoveID won't be called if xsltSetSourceNodeFlags has corrupted atype.
A use-after-free issue can be triggered within the xsltReleaseRVT function in variables.c. The attribute node is destroyed in [3], but due to its corrupted atype field, the ID table entry remains, and a later call to xmlFreeID (via xmlFreeIDTable[4]) attempts to update memory at a dangling reference[5].
Reproduction case
The reproduction case corrupts the atype field of an attribute node inside a result tree fragment by calling the key() function on it. When the fragment gets destroyed, a use-after-free occurs.
In stylesheet.xsl:
a1
In doc.xml:
./xsltproc stylesheet.xsl doc.xml
==3663074==ERROR: AddressSanitizer: heap-use-after-free on address 0x50b0000008e0 at pc 0x55c3e8ba6db2 bp 0x7ffc867b9ad0 sp 0x7ffc867b9ac8
WRITE of size 8 at 0x50b0000008e0 thread T0
#0 0x55c3e8ba6db1 in xmlFreeID libxml2/valid.c:2318
#1 0x55c3e8ba74e8 in xmlFreeIDTableEntry libxml2/valid.c:2462
#2 0x55c3e8aee710 in xmlHashFree libxml2/hash.c:241
#3 0x55c3e8ba750d in xmlFreeIDTable libxml2/valid.c:2473
#4 0x55c3e8a67c6b in xsltReleaseRVT libxslt/libxslt/variables.c:375
#5 0x55c3e8a68a48 in xsltFreeStackElem libxslt/libxslt/variables.c:581
#6 0x55c3e8a68e57 in xsltFreeStackElemList libxslt/libxslt/variables.c:631
#7 0x55c3e8a8932e in xsltLocalVariablePop libxslt/libxslt/transform.c:192
#8 0x55c3e8a93742 in xsltApplySequenceConstructor libxslt/libxslt/transform.c:3012
#9 0x55c3e8a941bb in xsltApplyXSLTTemplate libxslt/libxslt/transform.c:3219
#10 0x55c3e8a91578 in xsltProcessOneNode libxslt/libxslt/transform.c:2179
#11 0x55c3e8aa02fc in xsltApplyStylesheetInternal libxslt/libxslt/transform.c:6034
#12 0x55c3e8aa149f in xsltApplyStylesheetUser libxslt/libxslt/transform.c:6279
#13 0x55c3e8a31675 in xsltProcess libxslt/xsltproc/xsltproc.c:388
#14 0x55c3e8a353bf in main libxslt/xsltproc/xsltproc.c:882
#15 0x7f9392033d67 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#16 0x7f9392033e24 in __libc_start_main_impl ../csu/libc-start.c:360
#17 0x55c3e8a30800 in _start (libxslt/xsltproc/xsltproc+0x68800) (BuildId: dc57dc7f30c820dfc3c5fa6ad815c638ad36150a)
0x50b0000008e0 is located 96 bytes inside of 104-byte region [0x50b000000880,0x50b0000008e8)
freed by thread T0 here:
#0 0x7f93922f3978 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x55c3e8b6eb07 in xmlFreeProp libxml2/tree.c:1894
#2 0x55c3e8b6e854 in xmlFreePropList libxml2/tree.c:1867
#3 0x55c3e8b74148 in xmlFreeNodeList libxml2/tree.c:3621
#4 0x55c3e8a67bc7 in xsltReleaseRVT libxslt/libxslt/variables.c:370
#5 0x55c3e8a68a48 in xsltFreeStackElem libxslt/libxslt/variables.c:581
#6 0x55c3e8a68e57 in xsltFreeStackElemList libxslt/libxslt/variables.c:631
#7 0x55c3e8a8932e in xsltLocalVariablePop libxslt/libxslt/transform.c:192
#8 0x55c3e8a93742 in xsltApplySequenceConstructor libxslt/libxslt/transform.c:3012
#9 0x55c3e8a941bb in xsltApplyXSLTTemplate libxslt/libxslt/transform.c:3219
#10 0x55c3e8a91578 in xsltProcessOneNode libxslt/libxslt/transform.c:2179
#11 0x55c3e8aa02fc in xsltApplyStylesheetInternal libxslt/libxslt/transform.c:6034
#12 0x55c3e8aa149f in xsltApplyStylesheetUser libxslt/libxslt/transform.c:6279
#13 0x55c3e8a31675 in xsltProcess libxslt/xsltproc/xsltproc.c:388
#14 0x55c3e8a353bf in main libxslt/xsltproc/xsltproc.c:882
#15 0x7f9392033d67 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
previously allocated by thread T0 here:
#0 0x7f93922f4cd7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x55c3e8b6dda7 in xmlNewPropInternal libxml2/tree.c:1641
#2 0x55c3e8b81fc8 in xmlSetNsProp libxml2/tree.c:6947
#3 0x55c3e8a7e54b in xsltAttribute libxslt/libxslt/attributes.c:1031
#4 0x55c3e8a924d6 in xsltApplySequenceConstructor libxslt/libxslt/transform.c:2761
#5 0x55c3e8a9944f in xsltElement libxslt/libxslt/transform.c:4246
#6 0x55c3e8a924d6 in xsltApplySequenceConstructor libxslt/libxslt/transform.c:2761
#7 0x55c3e8a94688 in xsltApplyOneTemplate libxslt/libxslt/transform.c:3344
#8 0x55c3e8a6a457 in xsltEvalVariable libxslt/libxslt/variables.c:987
#9 0x55c3e8a6d641 in xsltBuildVariable libxslt/libxslt/variables.c:1807
#10 0x55c3e8a6d89b in xsltRegisterVariable libxslt/libxslt/variables.c:1868
#11 0x55c3e8a6e45b in xsltParseStylesheetVariable libxslt/libxslt/variables.c:2194
#12 0x55c3e8a92689 in xsltApplySequenceConstructor libxslt/libxslt/transform.c:2780
#13 0x55c3e8a941bb in xsltApplyXSLTTemplate libxslt/libxslt/transform.c:3219
#14 0x55c3e8a91578 in xsltProcessOneNode libxslt/libxslt/transform.c:2179
#15 0x55c3e8aa02fc in xsltApplyStylesheetInternal libxslt/libxslt/transform.c:6034
#16 0x55c3e8aa149f in xsltApplyStylesheetUser libxslt/libxslt/transform.c:6279
#17 0x55c3e8a31675 in xsltProcess libxslt/xsltproc/xsltproc.c:388
#18 0x55c3e8a353bf in main libxslt/xsltproc/xsltproc.c:882
#19 0x7f9392033d67 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-use-after-free libxml2/valid.c:2318 in xmlFreeID
Version
The vulnerability was reproduced at commit c8b1ea4b89a9b81fa611f32c80f47df0c3b3b004.
Credit information
Sergei Glazunov of Google Project Zero
This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-07-13.
For more details, see the Project Zero vulnerability disclosure policy: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure-policy.html
Comments
----
Reported as https://gitlab.gnome.org/Teams/Releng/security/-/issues/174 and moved to https://gitlab.gnome.org/GNOME/libxslt/-/issues/140 by the maintainer.
----
This issue has been made public as of July 9, 2025 at https://gitlab.gnome.org/GNOME/libxslt/-/issues/140. The libxslt project is considered unmaintained, and the GNOME maintainer has stated there are no plans to fix this vulnerability from the project's side. A CVE was requested.
The Chromium team has prepared patches, but they are known to break ABI compatibility and are not suitable for upstream. Separately, Apple has provided a patch which they believe does not break ABI. This patch has been attached to the GitLab issue at the link above for other downstream vendors to use.
Credit: Sergei Glazunov