From: Antoine Jacoutot Subject: Re: libxslt patches To: Theo Buehler Cc: ports@openbsd.org Date: Sat, 30 Aug 2025 11:08:30 +0200 OK aja On Fri, Aug 29, 2025 at 04:56:09PM +0200, Theo Buehler wrote: > 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__ */ > +- -- Antoine