From: Theo Buehler Subject: libxslt patches To: ports@openbsd.org Cc: ajacoutot@openbsd.org Date: Fri, 29 Aug 2025 16:56:09 +0200 There's been a bit of noise about this recently so I looked and noticed we have not yet pulled in the available patches. It doesn't sound like a big deal since it appears untrusted style sheets are involved. Still, it seems like something we want to patch. https://vuxml.freebsd.org/freebsd/b0a3466f-5efc-11f0-ae84-99047d0a6bcc.html lists four issues, three of which are public and have patches from apple and google. Two of them are for libxslt and apply cleanly. Those are included below: https://gitlab.gnome.org/GNOME/libxslt/-/issues/139 https://gitlab.gnome.org/GNOME/libxslt/-/issues/144 Regress still passes. The third one is quite confusing since part of it seems to be about libxml2 and some of the fixes implicate binary compatibility. The apple patch doesn't apply cleanly to our port: https://gitlab.gnome.org/GNOME/libxslt/-/issues/140 I leave that one to the maintainer to sort out. Index: Makefile =================================================================== RCS file: /cvs/ports/textproc/libxslt/Makefile,v diff -u -p -r1.110 Makefile --- Makefile 15 Mar 2025 09:34:42 -0000 1.110 +++ Makefile 29 Aug 2025 13:17:48 -0000 @@ -2,7 +2,7 @@ COMMENT= XSLT C Library for GNOME GNOME_VERSION= 1.1.43 GNOME_PROJECT= libxslt -REVISION= 0 +REVISION= 1 SHARED_LIBS += xslt 4.2 # 2.43 SHARED_LIBS += exslt 9.8 # 8.24 Index: patches/patch-libxslt_functions_c =================================================================== RCS file: patches/patch-libxslt_functions_c diff -N patches/patch-libxslt_functions_c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-libxslt_functions_c 29 Aug 2025 13:17:48 -0000 @@ -0,0 +1,55 @@ +https://gitlab.gnome.org/GNOME/libxslt/-/issues/139 + +From 345d6826d0eae6f0a962456b8ed6f6a1bad0877d Mon Sep 17 00:00:00 2001 +From: David Kilzer +Date: Sat, 24 May 2025 15:06:42 -0700 +Subject: [PATCH] libxslt: Type confusion in xmlNode.psvi between stylesheet + and source nodes + +* libxslt/functions.c: +(xsltDocumentFunctionLoadDocument): +- Implement fix suggested by Ivan Fratric. This copies the xmlDoc, + calls xsltCleanupSourceDoc() to remove pvsi fields, then adds the + xmlDoc to tctxt->docList. +- Add error handling for functions that may return NULL. +* libxslt/transform.c: +- Remove static keyword so this can be called from + xsltDocumentFunctionLoadDocument(). +* libxslt/transformInternals.h: Add. +(xsltCleanupSourceDoc): Add declaration. + +Fixes #139. + +Index: libxslt/functions.c +--- libxslt/functions.c.orig ++++ libxslt/functions.c +@@ -34,6 +34,7 @@ + #include "numbersInternals.h" + #include "keys.h" + #include "documents.h" ++#include "transformInternals.h" + + #ifdef WITH_XSLT_DEBUG + #define WITH_XSLT_DEBUG_FUNCTION +@@ -125,7 +126,20 @@ xsltDocumentFunctionLoadDocument(xmlXPathParserContext + /* + * This selects the stylesheet's doc itself. + */ +- doc = tctxt->style->doc; ++ doc = xmlCopyDoc(tctxt->style->doc, 1); ++ if (doc == NULL) { ++ xsltTransformError(tctxt, NULL, NULL, ++ "document() : failed to copy style doc\n"); ++ goto out_fragment; ++ } ++ xsltCleanupSourceDoc(doc); /* Remove psvi fields. */ ++ idoc = xsltNewDocument(tctxt, doc); ++ if (idoc == NULL) { ++ xsltTransformError(tctxt, NULL, NULL, ++ "document() : failed to create xsltDocument\n"); ++ xmlFreeDoc(doc); ++ goto out_fragment; ++ } + } else { + goto out_fragment; + } Index: patches/patch-libxslt_transformInternals_h =================================================================== RCS file: patches/patch-libxslt_transformInternals_h diff -N patches/patch-libxslt_transformInternals_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-libxslt_transformInternals_h 29 Aug 2025 13:17:48 -0000 @@ -0,0 +1,35 @@ +https://gitlab.gnome.org/GNOME/libxslt/-/issues/139 + +From 345d6826d0eae6f0a962456b8ed6f6a1bad0877d Mon Sep 17 00:00:00 2001 +From: David Kilzer +Date: Sat, 24 May 2025 15:06:42 -0700 +Subject: [PATCH] libxslt: Type confusion in xmlNode.psvi between stylesheet + and source nodes + +* libxslt/functions.c: +(xsltDocumentFunctionLoadDocument): +- Implement fix suggested by Ivan Fratric. This copies the xmlDoc, + calls xsltCleanupSourceDoc() to remove pvsi fields, then adds the + xmlDoc to tctxt->docList. +- Add error handling for functions that may return NULL. +* libxslt/transform.c: +- Remove static keyword so this can be called from + xsltDocumentFunctionLoadDocument(). +* libxslt/transformInternals.h: Add. +(xsltCleanupSourceDoc): Add declaration. + +Fixes #139. + +Index: libxslt/transformInternals.h +--- libxslt/transformInternals.h.orig ++++ libxslt/transformInternals.h +@@ -0,0 +1,9 @@ ++/* ++ * Summary: set of internal interfaces for the XSLT engine transformation part. ++ * ++ * Copy: See Copyright for the status of this software. ++ * ++ * Author: David Kilzer ++ */ ++ ++void xsltCleanupSourceDoc(xmlDocPtr doc); Index: patches/patch-libxslt_transform_c =================================================================== RCS file: patches/patch-libxslt_transform_c diff -N patches/patch-libxslt_transform_c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-libxslt_transform_c 29 Aug 2025 13:17:48 -0000 @@ -0,0 +1,258 @@ +https://gitlab.gnome.org/GNOME/libxslt/-/issues/139 +https://gitlab.gnome.org/GNOME/libxslt/-/issues/144 + +From 345d6826d0eae6f0a962456b8ed6f6a1bad0877d Mon Sep 17 00:00:00 2001 +From: David Kilzer +Date: Sat, 24 May 2025 15:06:42 -0700 +Subject: [PATCH] libxslt: Type confusion in xmlNode.psvi between stylesheet + and source nodes + +* libxslt/functions.c: +(xsltDocumentFunctionLoadDocument): +- Implement fix suggested by Ivan Fratric. This copies the xmlDoc, + calls xsltCleanupSourceDoc() to remove pvsi fields, then adds the + xmlDoc to tctxt->docList. +- Add error handling for functions that may return NULL. +* libxslt/transform.c: +- Remove static keyword so this can be called from + xsltDocumentFunctionLoadDocument(). +* libxslt/transformInternals.h: Add. +(xsltCleanupSourceDoc): Add declaration. + +Fixes #139. + +From f94e7e9796edeb6f3bedd3fdb1099e9b556aea21 Mon Sep 17 00:00:00 2001 +From: Daniel Cheng +Date: Sat, 31 May 2025 00:15:24 -0700 +Subject: [PATCH] Use a dedicated node type to maintain the list of cached RVTs + +While evaluating a stylesheet, result value trees (result tree fragments +in the XSLT spec) are represented as xmlDocs and cached on the transform +context in a linked list, using xmlDoc's prev and next pointers to +maintain the list. + +However, XPath evaluations can inadvertently traverse these links, which +are an implementation detail and do not reflect the actual document +structure. Using a dedicated node type avoids these unintended +traversals. + +Index: libxslt/transform.c +--- libxslt/transform.c.orig ++++ libxslt/transform.c +@@ -43,6 +43,7 @@ + #include "xsltlocale.h" + #include "pattern.h" + #include "transform.h" ++#include "transformInternals.h" + #include "variables.h" + #include "numbersInternals.h" + #include "namespaces.h" +@@ -518,19 +519,20 @@ xsltTransformCacheFree(xsltTransformCachePtr cache) + /* + * Free tree fragments. + */ +- if (cache->RVT) { +- xmlDocPtr tmp, cur = cache->RVT; ++ if (cache->rvtList) { ++ xsltRVTListPtr tmp, cur = cache->rvtList; + while (cur) { + tmp = cur; +- cur = (xmlDocPtr) cur->next; +- if (tmp->_private != NULL) { ++ cur = cur->next; ++ if (tmp->RVT->_private != NULL) { + /* +- * Tree the document info. ++ * Free the document info. + */ +- xsltFreeDocumentKeys((xsltDocumentPtr) tmp->_private); +- xmlFree(tmp->_private); ++ xsltFreeDocumentKeys((xsltDocumentPtr) tmp->RVT->_private); ++ xmlFree(tmp->RVT->_private); + } +- xmlFreeDoc(tmp); ++ xmlFreeDoc(tmp->RVT); ++ xmlFree(tmp); + } + } + /* +@@ -2263,38 +2265,40 @@ xsltLocalVariablePush(xsltTransformContextPtr ctxt, + * are preserved; all other fragments are freed/cached. + */ + static void +-xsltReleaseLocalRVTs(xsltTransformContextPtr ctxt, xmlDocPtr base) ++xsltReleaseLocalRVTs(xsltTransformContextPtr ctxt, xsltRVTListPtr base) + { +- xmlDocPtr cur = ctxt->localRVT, tmp; ++ xsltRVTListPtr cur = ctxt->localRVTList, tmp; + + if (cur == base) + return; + if (cur->prev != NULL) + xsltTransformError(ctxt, NULL, NULL, "localRVT not head of list\n"); + +- /* Reset localRVT early because some RVTs might be registered again. */ +- ctxt->localRVT = base; ++ /* Reset localRVTList early because some RVTs might be registered again. */ ++ ctxt->localRVTList = base; + if (base != NULL) + base->prev = NULL; + + do { + tmp = cur; +- cur = (xmlDocPtr) cur->next; +- if (tmp->compression == XSLT_RVT_LOCAL) { +- xsltReleaseRVT(ctxt, tmp); +- } else if (tmp->compression == XSLT_RVT_GLOBAL) { +- xsltRegisterPersistRVT(ctxt, tmp); +- } else if (tmp->compression == XSLT_RVT_FUNC_RESULT) { ++ cur = cur->next; ++ if (tmp->RVT->compression == XSLT_RVT_LOCAL) { ++ xsltReleaseRVTList(ctxt, tmp); ++ } else if (tmp->RVT->compression == XSLT_RVT_GLOBAL) { ++ xsltRegisterPersistRVT(ctxt, tmp->RVT); ++ xmlFree(tmp); ++ } else if (tmp->RVT->compression == XSLT_RVT_FUNC_RESULT) { + /* + * This will either register the RVT again or move it to the + * context variable. + */ +- xsltRegisterLocalRVT(ctxt, tmp); +- tmp->compression = XSLT_RVT_FUNC_RESULT; ++ xsltRegisterLocalRVT(ctxt, tmp->RVT); ++ tmp->RVT->compression = XSLT_RVT_FUNC_RESULT; ++ xmlFree(tmp); + } else { + xmlGenericError(xmlGenericErrorContext, +- "xsltReleaseLocalRVTs: Unexpected RVT flag %p\n", +- tmp->psvi); ++ "xsltReleaseLocalRVTs: Unexpected RVT flag %d\n", ++ tmp->RVT->compression); + } + } while (cur != base); + } +@@ -2322,7 +2326,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + xmlNodePtr oldInsert, oldInst, oldCurInst, oldContextNode; + xmlNodePtr cur, insert, copy = NULL; + int level = 0, oldVarsNr; +- xmlDocPtr oldLocalFragmentTop; ++ xsltRVTListPtr oldLocalFragmentTop; + + #ifdef XSLT_REFACTORED + xsltStylePreCompPtr info; +@@ -2368,7 +2372,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + } + ctxt->depth++; + +- oldLocalFragmentTop = ctxt->localRVT; ++ oldLocalFragmentTop = ctxt->localRVTList; + oldInsert = insert = ctxt->insert; + oldInst = oldCurInst = ctxt->inst; + oldContextNode = ctxt->node; +@@ -2602,7 +2606,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + /* + * Cleanup temporary tree fragments. + */ +- if (oldLocalFragmentTop != ctxt->localRVT) ++ if (oldLocalFragmentTop != ctxt->localRVTList) + xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); + + ctxt->insert = oldInsert; +@@ -2697,7 +2701,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + /* + * Cleanup temporary tree fragments. + */ +- if (oldLocalFragmentTop != ctxt->localRVT) ++ if (oldLocalFragmentTop != ctxt->localRVTList) + xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); + + ctxt->insert = oldInsert; +@@ -2763,7 +2767,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + /* + * Cleanup temporary tree fragments. + */ +- if (oldLocalFragmentTop != ctxt->localRVT) ++ if (oldLocalFragmentTop != ctxt->localRVTList) + xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); + + ctxt->insert = oldInsert; +@@ -2893,7 +2897,7 @@ xsltApplySequenceConstructor(xsltTransformContextPtr c + /* + * Cleanup temporary tree fragments. + */ +- if (oldLocalFragmentTop != ctxt->localRVT) ++ if (oldLocalFragmentTop != ctxt->localRVTList) + xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); + + ctxt->insert = oldInsert; +@@ -3072,7 +3076,7 @@ xsltApplyXSLTTemplate(xsltTransformContextPtr ctxt, + int oldVarsBase = 0; + xmlNodePtr cur; + xsltStackElemPtr tmpParam = NULL; +- xmlDocPtr oldUserFragmentTop; ++ xsltRVTListPtr oldUserFragmentTop; + #ifdef WITH_PROFILER + long start = 0; + #endif +@@ -3120,8 +3124,8 @@ xsltApplyXSLTTemplate(xsltTransformContextPtr ctxt, + return; + } + +- oldUserFragmentTop = ctxt->tmpRVT; +- ctxt->tmpRVT = NULL; ++ oldUserFragmentTop = ctxt->tmpRVTList; ++ ctxt->tmpRVTList = NULL; + + /* + * Initiate a distinct scope of local params/variables. +@@ -3232,16 +3236,16 @@ xsltApplyXSLTTemplate(xsltTransformContextPtr ctxt, + * user code should now use xsltRegisterLocalRVT() instead + * of the obsolete xsltRegisterTmpRVT(). + */ +- if (ctxt->tmpRVT) { +- xmlDocPtr curdoc = ctxt->tmpRVT, tmp; ++ if (ctxt->tmpRVTList) { ++ xsltRVTListPtr curRVTList = ctxt->tmpRVTList, tmp; + +- while (curdoc != NULL) { +- tmp = curdoc; +- curdoc = (xmlDocPtr) curdoc->next; +- xsltReleaseRVT(ctxt, tmp); ++ while (curRVTList != NULL) { ++ tmp = curRVTList; ++ curRVTList = curRVTList->next; ++ xsltReleaseRVTList(ctxt, tmp); + } + } +- ctxt->tmpRVT = oldUserFragmentTop; ++ ctxt->tmpRVTList = oldUserFragmentTop; + + /* + * Pop the xsl:template declaration from the stack. +@@ -5319,7 +5323,7 @@ xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr contex + + #ifdef XSLT_FAST_IF + { +- xmlDocPtr oldLocalFragmentTop = ctxt->localRVT; ++ xsltRVTListPtr oldLocalFragmentTop = ctxt->localRVTList; + + res = xsltPreCompEvalToBoolean(ctxt, contextNode, comp); + +@@ -5327,7 +5331,7 @@ xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr contex + * Cleanup fragments created during evaluation of the + * "select" expression. + */ +- if (oldLocalFragmentTop != ctxt->localRVT) ++ if (oldLocalFragmentTop != ctxt->localRVTList) + xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); + } + +@@ -5757,7 +5761,7 @@ xsltCountKeys(xsltTransformContextPtr ctxt) + * + * Resets source node flags and ids stored in 'psvi' member. + */ +-static void ++void + xsltCleanupSourceDoc(xmlDocPtr doc) { + xmlNodePtr cur = (xmlNodePtr) doc; + void **psviPtr; Index: patches/patch-libxslt_variables_c =================================================================== RCS file: patches/patch-libxslt_variables_c diff -N patches/patch-libxslt_variables_c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-libxslt_variables_c 29 Aug 2025 13:17:49 -0000 @@ -0,0 +1,428 @@ +https://gitlab.gnome.org/GNOME/libxslt/-/issues/144 + +From f94e7e9796edeb6f3bedd3fdb1099e9b556aea21 Mon Sep 17 00:00:00 2001 +From: Daniel Cheng +Date: Sat, 31 May 2025 00:15:24 -0700 +Subject: [PATCH] Use a dedicated node type to maintain the list of cached RVTs + +While evaluating a stylesheet, result value trees (result tree fragments +in the XSLT spec) are represented as xmlDocs and cached on the transform +context in a linked list, using xmlDoc's prev and next pointers to +maintain the list. + +However, XPath evaluations can inadvertently traverse these links, which +are an implementation detail and do not reflect the actual document +structure. Using a dedicated node type avoids these unintended +traversals. + +Index: libxslt/variables.c +--- libxslt/variables.c.orig ++++ libxslt/variables.c +@@ -47,6 +47,21 @@ static const xmlChar *xsltComputingGlobalVarMarker = + #define XSLT_VAR_IN_SELECT (1<<1) + #define XSLT_TCTXT_VARIABLE(c) ((xsltStackElemPtr) (c)->contextVariable) + ++static xsltRVTListPtr ++xsltRVTListCreate(void) ++{ ++ xsltRVTListPtr ret; ++ ++ ret = (xsltRVTListPtr) xmlMalloc(sizeof(xsltRVTList)); ++ if (ret == NULL) { ++ xsltTransformError(NULL, NULL, NULL, ++ "xsltRVTListCreate: malloc failed\n"); ++ return(NULL); ++ } ++ memset(ret, 0, sizeof(xsltRVTList)); ++ return(ret); ++} ++ + /************************************************************************ + * * + * Result Value Tree (Result Tree Fragment) interfaces * +@@ -64,6 +79,7 @@ static const xmlChar *xsltComputingGlobalVarMarker = + xmlDocPtr + xsltCreateRVT(xsltTransformContextPtr ctxt) + { ++ xsltRVTListPtr rvtList; + xmlDocPtr container; + + /* +@@ -76,12 +92,11 @@ xsltCreateRVT(xsltTransformContextPtr ctxt) + /* + * Reuse a RTF from the cache if available. + */ +- if (ctxt->cache->RVT) { +- container = ctxt->cache->RVT; +- ctxt->cache->RVT = (xmlDocPtr) container->next; +- /* clear the internal pointers */ +- container->next = NULL; +- container->prev = NULL; ++ if (ctxt->cache->rvtList) { ++ rvtList = ctxt->cache->rvtList; ++ container = ctxt->cache->rvtList->RVT; ++ ctxt->cache->rvtList = rvtList->next; ++ xmlFree(rvtList); + if (ctxt->cache->nbRVT > 0) + ctxt->cache->nbRVT--; + #ifdef XSLT_DEBUG_PROFILE_CACHE +@@ -119,11 +134,17 @@ xsltCreateRVT(xsltTransformContextPtr ctxt) + int + xsltRegisterTmpRVT(xsltTransformContextPtr ctxt, xmlDocPtr RVT) + { ++ xsltRVTListPtr list; ++ + if ((ctxt == NULL) || (RVT == NULL)) + return(-1); + +- RVT->prev = NULL; ++ list = xsltRVTListCreate(); ++ if (list == NULL) return(-1); ++ + RVT->compression = XSLT_RVT_LOCAL; ++ list->prev = NULL; ++ list->RVT = RVT; + + /* + * We'll restrict the lifetime of user-created fragments +@@ -131,15 +152,15 @@ xsltRegisterTmpRVT(xsltTransformContextPtr ctxt, xmlDo + * var/param itself. + */ + if (ctxt->contextVariable != NULL) { +- RVT->next = (xmlNodePtr) XSLT_TCTXT_VARIABLE(ctxt)->fragment; +- XSLT_TCTXT_VARIABLE(ctxt)->fragment = RVT; ++ list->next = XSLT_TCTXT_VARIABLE(ctxt)->fragment; ++ XSLT_TCTXT_VARIABLE(ctxt)->fragment = list; + return(0); + } + +- RVT->next = (xmlNodePtr) ctxt->tmpRVT; +- if (ctxt->tmpRVT != NULL) +- ctxt->tmpRVT->prev = (xmlNodePtr) RVT; +- ctxt->tmpRVT = RVT; ++ list->next = ctxt->tmpRVTList; ++ if (ctxt->tmpRVTList != NULL) ++ ctxt->tmpRVTList->prev = list; ++ ctxt->tmpRVTList = list; + return(0); + } + +@@ -159,11 +180,17 @@ int + xsltRegisterLocalRVT(xsltTransformContextPtr ctxt, + xmlDocPtr RVT) + { ++ xsltRVTListPtr list; ++ + if ((ctxt == NULL) || (RVT == NULL)) + return(-1); + +- RVT->prev = NULL; ++ list = xsltRVTListCreate(); ++ if (list == NULL) return(-1); ++ + RVT->compression = XSLT_RVT_LOCAL; ++ list->prev = NULL; ++ list->RVT = RVT; + + /* + * When evaluating "select" expressions of xsl:variable +@@ -174,8 +201,8 @@ xsltRegisterLocalRVT(xsltTransformContextPtr ctxt, + if ((ctxt->contextVariable != NULL) && + (XSLT_TCTXT_VARIABLE(ctxt)->flags & XSLT_VAR_IN_SELECT)) + { +- RVT->next = (xmlNodePtr) XSLT_TCTXT_VARIABLE(ctxt)->fragment; +- XSLT_TCTXT_VARIABLE(ctxt)->fragment = RVT; ++ list->next = XSLT_TCTXT_VARIABLE(ctxt)->fragment; ++ XSLT_TCTXT_VARIABLE(ctxt)->fragment = list; + return(0); + } + /* +@@ -183,10 +210,10 @@ xsltRegisterLocalRVT(xsltTransformContextPtr ctxt, + * If not reference by a returning instruction (like EXSLT's function), + * then this fragment will be freed, when the instruction exits. + */ +- RVT->next = (xmlNodePtr) ctxt->localRVT; +- if (ctxt->localRVT != NULL) +- ctxt->localRVT->prev = (xmlNodePtr) RVT; +- ctxt->localRVT = RVT; ++ list->next = ctxt->localRVTList; ++ if (ctxt->localRVTList != NULL) ++ ctxt->localRVTList->prev = list; ++ ctxt->localRVTList = list; + return(0); + } + +@@ -344,8 +371,9 @@ xsltFlagRVTs(xsltTransformContextPtr ctxt, xmlXPathObj + * @ctxt: an XSLT transformation context + * @RVT: a result value tree (Result Tree Fragment) + * +- * Either frees the RVT (which is an xmlDoc) or stores +- * it in the context's cache for later reuse. ++ * Either frees the RVT (which is an xmlDoc) or stores it in the context's ++ * cache for later reuse. Preserved for ABI/API compatibility; internal use ++ * has all migrated to xsltReleaseRVTList(). + */ + void + xsltReleaseRVT(xsltTransformContextPtr ctxt, xmlDocPtr RVT) +@@ -353,36 +381,68 @@ xsltReleaseRVT(xsltTransformContextPtr ctxt, xmlDocPtr + if (RVT == NULL) + return; + ++ xsltRVTListPtr list = xsltRVTListCreate(); ++ if (list == NULL) { ++ if (RVT->_private != NULL) { ++ xsltFreeDocumentKeys((xsltDocumentPtr) RVT->_private); ++ xmlFree(RVT->_private); ++ } ++ xmlFreeDoc(RVT); ++ return; ++ } ++ ++ xsltReleaseRVTList(ctxt, list); ++} ++ ++/** ++ * xsltReleaseRVTList: ++ * @ctxt: an XSLT transformation context ++ * @list: a list node containing a result value tree (Result Tree Fragment) ++ * ++ * Either frees the list node or stores it in the context's cache for later ++ * reuse. Optimization to avoid adding a fallible allocation path when the ++ * caller already has a RVT list node. ++ */ ++void ++xsltReleaseRVTList(xsltTransformContextPtr ctxt, xsltRVTListPtr list) ++{ ++ if (list == NULL) ++ return; ++ + if (ctxt && (ctxt->cache->nbRVT < 40)) { + /* + * Store the Result Tree Fragment. + * Free the document info. + */ +- if (RVT->_private != NULL) { +- xsltFreeDocumentKeys((xsltDocumentPtr) RVT->_private); +- xmlFree(RVT->_private); +- RVT->_private = NULL; ++ if (list->RVT->_private != NULL) { ++ xsltFreeDocumentKeys((xsltDocumentPtr) list->RVT->_private); ++ xmlFree(list->RVT->_private); ++ list->RVT->_private = NULL; + } + /* + * Clear the document tree. + */ +- if (RVT->children != NULL) { +- xmlFreeNodeList(RVT->children); +- RVT->children = NULL; +- RVT->last = NULL; ++ if (list->RVT->children != NULL) { ++ xmlFreeNodeList(list->RVT->children); ++ list->RVT->children = NULL; ++ list->RVT->last = NULL; + } +- if (RVT->ids != NULL) { +- xmlFreeIDTable((xmlIDTablePtr) RVT->ids); +- RVT->ids = NULL; ++ if (list->RVT->ids != NULL) { ++ xmlFreeIDTable((xmlIDTablePtr) list->RVT->ids); ++ list->RVT->ids = NULL; + } + + /* + * Reset the ownership information. + */ +- RVT->compression = 0; ++ list->RVT->compression = 0; + +- RVT->next = (xmlNodePtr) ctxt->cache->RVT; +- ctxt->cache->RVT = RVT; ++ /* ++ * prev is not set nor used in the cache, since elements are only ++ * pushed/popped from the front of the list. ++ */ ++ list->next = ctxt->cache->rvtList; ++ ctxt->cache->rvtList = list; + + ctxt->cache->nbRVT++; + +@@ -394,11 +454,12 @@ xsltReleaseRVT(xsltTransformContextPtr ctxt, xmlDocPtr + /* + * Free it. + */ +- if (RVT->_private != NULL) { +- xsltFreeDocumentKeys((xsltDocumentPtr) RVT->_private); +- xmlFree(RVT->_private); ++ if (list->RVT->_private != NULL) { ++ xsltFreeDocumentKeys((xsltDocumentPtr) list->RVT->_private); ++ xmlFree(list->RVT->_private); + } +- xmlFreeDoc(RVT); ++ xmlFreeDoc(list->RVT); ++ xmlFree(list); + } + + /** +@@ -416,14 +477,20 @@ xsltReleaseRVT(xsltTransformContextPtr ctxt, xmlDocPtr + int + xsltRegisterPersistRVT(xsltTransformContextPtr ctxt, xmlDocPtr RVT) + { ++ xsltRVTListPtr list; ++ + if ((ctxt == NULL) || (RVT == NULL)) return(-1); + ++ list = xsltRVTListCreate(); ++ if (list == NULL) return(-1); ++ + RVT->compression = XSLT_RVT_GLOBAL; +- RVT->prev = NULL; +- RVT->next = (xmlNodePtr) ctxt->persistRVT; +- if (ctxt->persistRVT != NULL) +- ctxt->persistRVT->prev = (xmlNodePtr) RVT; +- ctxt->persistRVT = RVT; ++ list->RVT = RVT; ++ list->prev = NULL; ++ list->next = ctxt->persistRVTList; ++ if (ctxt->persistRVTList != NULL) ++ ctxt->persistRVTList->prev = list; ++ ctxt->persistRVTList = list; + return(0); + } + +@@ -438,52 +505,55 @@ xsltRegisterPersistRVT(xsltTransformContextPtr ctxt, x + void + xsltFreeRVTs(xsltTransformContextPtr ctxt) + { +- xmlDocPtr cur, next; ++ xsltRVTListPtr cur, next; + + if (ctxt == NULL) + return; + /* + * Local fragments. + */ +- cur = ctxt->localRVT; ++ cur = ctxt->localRVTList; + while (cur != NULL) { +- next = (xmlDocPtr) cur->next; +- if (cur->_private != NULL) { +- xsltFreeDocumentKeys(cur->_private); +- xmlFree(cur->_private); ++ next = cur->next; ++ if (cur->RVT->_private != NULL) { ++ xsltFreeDocumentKeys(cur->RVT->_private); ++ xmlFree(cur->RVT->_private); + } +- xmlFreeDoc(cur); ++ xmlFreeDoc(cur->RVT); ++ xmlFree(cur); + cur = next; + } +- ctxt->localRVT = NULL; ++ ctxt->localRVTList = NULL; + /* + * User-created per-template fragments. + */ +- cur = ctxt->tmpRVT; ++ cur = ctxt->tmpRVTList; + while (cur != NULL) { +- next = (xmlDocPtr) cur->next; +- if (cur->_private != NULL) { +- xsltFreeDocumentKeys(cur->_private); +- xmlFree(cur->_private); ++ next = cur->next; ++ if (cur->RVT->_private != NULL) { ++ xsltFreeDocumentKeys(cur->RVT->_private); ++ xmlFree(cur->RVT->_private); + } +- xmlFreeDoc(cur); ++ xmlFreeDoc(cur->RVT); ++ xmlFree(cur); + cur = next; + } +- ctxt->tmpRVT = NULL; ++ ctxt->tmpRVTList = NULL; + /* + * Global fragments. + */ +- cur = ctxt->persistRVT; ++ cur = ctxt->persistRVTList; + while (cur != NULL) { +- next = (xmlDocPtr) cur->next; +- if (cur->_private != NULL) { +- xsltFreeDocumentKeys(cur->_private); +- xmlFree(cur->_private); ++ next = cur->next; ++ if (cur->RVT->_private != NULL) { ++ xsltFreeDocumentKeys(cur->RVT->_private); ++ xmlFree(cur->RVT->_private); + } +- xmlFreeDoc(cur); ++ xmlFreeDoc(cur->RVT); ++ xmlFree(cur); + cur = next; + } +- ctxt->persistRVT = NULL; ++ ctxt->persistRVTList = NULL; + } + + /************************************************************************ +@@ -571,21 +641,22 @@ xsltFreeStackElem(xsltStackElemPtr elem) { + * Release the list of temporary Result Tree Fragments. + */ + if (elem->context) { +- xmlDocPtr cur; ++ xsltRVTListPtr cur; + + while (elem->fragment != NULL) { + cur = elem->fragment; +- elem->fragment = (xmlDocPtr) cur->next; ++ elem->fragment = cur->next; + +- if (cur->compression == XSLT_RVT_LOCAL) { +- xsltReleaseRVT(elem->context, cur); +- } else if (cur->compression == XSLT_RVT_FUNC_RESULT) { +- xsltRegisterLocalRVT(elem->context, cur); +- cur->compression = XSLT_RVT_FUNC_RESULT; ++ if (cur->RVT->compression == XSLT_RVT_LOCAL) { ++ xsltReleaseRVTList(elem->context, cur); ++ } else if (cur->RVT->compression == XSLT_RVT_FUNC_RESULT) { ++ xsltRegisterLocalRVT(elem->context, cur->RVT); ++ cur->RVT->compression = XSLT_RVT_FUNC_RESULT; ++ xmlFree(cur); + } else { + xmlGenericError(xmlGenericErrorContext, + "xsltFreeStackElem: Unexpected RVT flag %d\n", +- cur->compression); ++ cur->RVT->compression); + } + } + } +@@ -944,6 +1015,7 @@ xsltEvalVariable(xsltTransformContextPtr ctxt, xsltSta + } else { + if (variable->tree) { + xmlDocPtr container; ++ xsltRVTListPtr rvtList; + xmlNodePtr oldInsert; + xmlDocPtr oldOutput; + const xmlChar *oldLastText; +@@ -968,7 +1040,11 @@ xsltEvalVariable(xsltTransformContextPtr ctxt, xsltSta + * when the variable is freed, it will also free + * the Result Tree Fragment. + */ +- variable->fragment = container; ++ rvtList = xsltRVTListCreate(); ++ if (rvtList == NULL) ++ goto error; ++ rvtList->RVT = container; ++ variable->fragment = rvtList; + container->compression = XSLT_RVT_LOCAL; + + oldOutput = ctxt->output; +@@ -2361,5 +2437,3 @@ local_variable_found: + + return(valueObj); + } +- +- Index: patches/patch-libxslt_xsltInternals_h =================================================================== RCS file: patches/patch-libxslt_xsltInternals_h diff -N patches/patch-libxslt_xsltInternals_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-libxslt_xsltInternals_h 29 Aug 2025 13:17:49 -0000 @@ -0,0 +1,94 @@ +https://gitlab.gnome.org/GNOME/libxslt/-/issues/144 + +From f94e7e9796edeb6f3bedd3fdb1099e9b556aea21 Mon Sep 17 00:00:00 2001 +From: Daniel Cheng +Date: Sat, 31 May 2025 00:15:24 -0700 +Subject: [PATCH] Use a dedicated node type to maintain the list of cached RVTs + +While evaluating a stylesheet, result value trees (result tree fragments +in the XSLT spec) are represented as xmlDocs and cached on the transform +context in a linked list, using xmlDoc's prev and next pointers to +maintain the list. + +However, XPath evaluations can inadvertently traverse these links, which +are an implementation detail and do not reflect the actual document +structure. Using a dedicated node type avoids these unintended +traversals. + +Index: libxslt/xsltInternals.h +--- libxslt/xsltInternals.h.orig ++++ libxslt/xsltInternals.h +@@ -1410,6 +1410,8 @@ struct _xsltStylePreComp { + + #endif /* XSLT_REFACTORED */ + ++typedef struct _xsltRVTList xsltRVTList; ++typedef xsltRVTList *xsltRVTListPtr; + + /* + * The in-memory structure corresponding to an XSLT Variable +@@ -1427,7 +1429,7 @@ struct _xsltStackElem { + xmlNodePtr tree; /* the sequence constructor if no eval + string or the location */ + xmlXPathObjectPtr value; /* The value if computed */ +- xmlDocPtr fragment; /* The Result Tree Fragments (needed for XSLT 1.0) ++ xsltRVTListPtr fragment; /* The Result Tree Fragments (needed for XSLT 1.0) + which are bound to the variable's lifetime. */ + int level; /* the depth in the tree; + -1 if persistent (e.g. a given xsl:with-param) */ +@@ -1639,10 +1641,16 @@ struct _xsltStylesheet { + unsigned long opCount; + }; + ++struct _xsltRVTList { ++ xmlDocPtr RVT; ++ xsltRVTListPtr prev; ++ xsltRVTListPtr next; ++}; ++ + typedef struct _xsltTransformCache xsltTransformCache; + typedef xsltTransformCache *xsltTransformCachePtr; + struct _xsltTransformCache { +- xmlDocPtr RVT; ++ xsltRVTListPtr rvtList; + int nbRVT; + xsltStackElemPtr stackItems; + int nbStackItems; +@@ -1749,8 +1757,8 @@ struct _xsltTransformContext { + * handling of temporary Result Value Tree + * (XSLT 1.0 term: "Result Tree Fragment") + */ +- xmlDocPtr tmpRVT; /* list of RVT without persistance */ +- xmlDocPtr persistRVT; /* list of persistant RVTs */ ++ xsltRVTListPtr tmpRVTList; /* list of RVT without persistance */ ++ xsltRVTListPtr persistRVTList; /* list of persistant RVTs */ + int ctxtflags; /* context processing flags */ + + /* +@@ -1783,7 +1791,7 @@ struct _xsltTransformContext { + xmlDocPtr initialContextDoc; + xsltTransformCachePtr cache; + void *contextVariable; /* the current variable item */ +- xmlDocPtr localRVT; /* list of local tree fragments; will be freed when ++ xsltRVTListPtr localRVTList; /* list of local tree fragments; will be freed when + the instruction which created the fragment + exits */ + xmlDocPtr localRVTBase; /* Obsolete */ +@@ -1932,8 +1940,11 @@ XSLTPUBFUN int XSLTCALL + XSLTPUBFUN void XSLTCALL + xsltFreeRVTs (xsltTransformContextPtr ctxt); + XSLTPUBFUN void XSLTCALL +- xsltReleaseRVT (xsltTransformContextPtr ctxt, ++ xsltReleaseRVT (xsltTransformContextPtr ctxt, + xmlDocPtr RVT); ++XSLTPUBFUN void XSLTCALL ++ xsltReleaseRVTList (xsltTransformContextPtr ctxt, ++ xsltRVTListPtr list); + /* + * Extra functions for Attribute Value Templates + */ +@@ -1992,4 +2003,3 @@ XSLTPUBFUN int XSLTCALL + #endif + + #endif /* __XML_XSLT_H__ */ +-