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.