This is an automated email from the ASF dual-hosted git repository.

rubenada pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 1544c1b1b0 [CALCITE-7790] XmlFunctions: XMLTRANSFORM should parse its 
XML argument through the shared DocumentBuilder and set explicit JAXP 
external-access attributes on the TransformerFactory
1544c1b1b0 is described below

commit 1544c1b1b0b6039f7720695338981460779191c6
Author: Ruben Quesada Lopez <[email protected]>
AuthorDate: Thu Sep 17 15:52:35 2026 +0100

    [CALCITE-7790] XmlFunctions: XMLTRANSFORM should parse its XML argument 
through the shared DocumentBuilder and set explicit JAXP external-access 
attributes on the TransformerFactory
---
 .../org/apache/calcite/runtime/XmlFunctions.java   | 13 +++++++-
 .../apache/calcite/test/SqlXmlFunctionsTest.java   | 38 ++++++++++++++++++++++
 site/_docs/security_threat_model.md                |  6 ++--
 3 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
index 00660a1b46..f2e1992657 100644
--- a/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/XmlFunctions.java
@@ -86,6 +86,13 @@ public class XmlFunctions {
         } catch (TransformerConfigurationException e) {
           throw new IllegalStateException("Transformer Factory configuration 
failed", e);
         }
+        try {
+          transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, 
"");
+          
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+        } catch (IllegalArgumentException e) {
+          throw new IllegalStateException("Transformer Factory does not 
support restricting"
+              + " access to external DTDs and stylesheets", e);
+        }
         return transformerFactory;
       });
 
@@ -147,14 +154,18 @@ private XmlFunctions() {
     }
     try {
       final Source xsltSource = new StreamSource(new StringReader(xslt));
-      final Source xmlSource = new StreamSource(new StringReader(xml));
       final Transformer transformer =
           TRANSFORMER_FACTORY.get().newTransformer(xsltSource);
+      final Source xmlSource = new DOMSource(getDocumentNode(xml));
       final StringWriter writer = new StringWriter();
       final StreamResult result = new StreamResult(writer);
       transformer.setErrorListener(new InternalErrorListener());
       transformer.transform(xmlSource, result);
       return writer.toString();
+    } catch (IllegalArgumentException e) {
+      // getDocumentNode rejected the XML argument (e.g. it contains a
+      // DOCTYPE declaration, or is not well-formed).
+      throw RESOURCE.invalidInputForXmlTransform(xml).ex();
     } catch (TransformerConfigurationException e) {
       throw RESOURCE.illegalXslt(xslt).ex();
     } catch (TransformerException e) {
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlXmlFunctionsTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlXmlFunctionsTest.java
index 046d935705..52cd4a707d 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlXmlFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlXmlFunctionsTest.java
@@ -94,6 +94,44 @@ class SqlXmlFunctionsTest {
     assertXmlTransformFailed(XML, xsltExternalEntity, 
Matchers.expectThrowable(expected));
   }
 
+  @Test void testXmlTransformDocumentFunctionDenied() {
+    String xslt = "<xsl:stylesheet version=\"1.0\""
+        + " xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>"
+        + "<xsl:template match=\"/\">"
+        + "<xsl:copy-of select=\"document('file:///dev/null')\"/>"
+        + "</xsl:template></xsl:stylesheet>";
+    String message = "Invalid input for XMLTRANSFORM xml: '" + XML + "'";
+    CalciteException expected = new CalciteException(message, null);
+    assertXmlTransformFailed(XML, xslt, Matchers.expectThrowable(expected));
+  }
+
+  @Test void testXmlTransformXslIncludeDenied() {
+    String xslt = "<xsl:stylesheet version=\"1.0\""
+        + " xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>"
+        + "<xsl:include href=\"file:///dev/null\"/>"
+        + "</xsl:stylesheet>";
+    String message = "Illegal xslt specified : '" + xslt + "'";
+    CalciteException expected = new CalciteException(message, null);
+    assertXmlTransformFailed(XML, xslt, Matchers.expectThrowable(expected));
+  }
+
+  @Test void testXmlTransformXslImportDenied() {
+    String xslt = "<xsl:stylesheet version=\"1.0\""
+        + " xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\";>"
+        + "<xsl:import href=\"file:///dev/null\"/>"
+        + "</xsl:stylesheet>";
+    String message = "Illegal xslt specified : '" + xslt + "'";
+    CalciteException expected = new CalciteException(message, null);
+    assertXmlTransformFailed(XML, xslt, Matchers.expectThrowable(expected));
+  }
+
+  @Test void testXmlTransformDoctypeDenied() {
+    String xml = "<!DOCTYPE document [<!ENTITY a 
\"b\">]><document>&a;</document>";
+    String message = "Invalid input for XMLTRANSFORM xml: '" + xml + "'";
+    CalciteException expected = new CalciteException(message, null);
+    assertXmlTransformFailed(xml, XSLT, Matchers.expectThrowable(expected));
+  }
+
   @Test void testXmlTransform() {
     assertXmlTransform(null, "", nullValue());
     assertXmlTransform("", null, nullValue());
diff --git a/site/_docs/security_threat_model.md 
b/site/_docs/security_threat_model.md
index acc22f367b..1b5e72493b 100644
--- a/site/_docs/security_threat_model.md
+++ b/site/_docs/security_threat_model.md
@@ -240,9 +240,9 @@ ## Denial of service
   carries a `CancelFlag` in the planner context, so a deadline can build on it;
   the firing and size caps are new.
 * **Execution.** Catastrophic regex backtracking in `LIKE`, `SIMILAR TO`, or
-  `RLIKE`, or an unbounded join, exhausts resources at run time. Planning 
bounds
-  do not help here; the mitigation is a match-time limit or a backtracking-free
-  regex engine.
+  `RLIKE`, an unbounded XSLT program in `XMLTRANSFORM`, or an unbounded join,
+  exhausts resources at run time. Planning bounds do not help here; the
+  mitigation is a match-time limit or a backtracking-free regex engine.
 * **Parsing.** Deeply nested expressions can overflow the parser stack. The
   mitigation is a nesting-depth limit.
 

Reply via email to