Download raw body.
libxslt patches
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 <ddkilzer@apple.com>
> +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 <ddkilzer@apple.com>
> +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 <ddkilzer@apple.com>
> ++ */
> ++
> ++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 <ddkilzer@apple.com>
> +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 <dcheng@chromium.org>
> +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 <dcheng@chromium.org>
> +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 <dcheng@chromium.org>
> +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
libxslt patches