Index | Thread | Search

From:
Antoine Jacoutot <ajacoutot@bsdfrog.org>
Subject:
Re: libxslt patches
To:
Theo Buehler <tb@theobuehler.org>
Cc:
ports@openbsd.org
Date:
Sat, 30 Aug 2025 11:08:30 +0200

Download raw body.

Thread
  • Theo Buehler:

    libxslt patches

    • Antoine Jacoutot:

      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