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 00660a1b464..f2e19926576 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 046d9357059..52cd4a707df 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 = ""
+ + ""
+ + ""
+ + "";
+ 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 = ""
+ + ""
+ + "";
+ String message = "Illegal xslt specified : '" + xslt + "'";
+ CalciteException expected = new CalciteException(message, null);
+ assertXmlTransformFailed(XML, xslt, Matchers.expectThrowable(expected));
+ }
+
+ @Test void testXmlTransformXslImportDenied() {
+ String xslt = ""
+ + ""
+ + "";
+ String message = "Illegal xslt specified : '" + xslt + "'";
+ CalciteException expected = new CalciteException(message, null);
+ assertXmlTransformFailed(XML, xslt, Matchers.expectThrowable(expected));
+ }
+
+ @Test void testXmlTransformDoctypeDenied() {
+ String xml = "]>&a;";
+ 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 acc22f367bb..1b5e72493b0 100644
--- a/site/_docs/security_threat_model.md
+++ b/site/_docs/security_threat_model.md
@@ -240,9 +240,9 @@ land.
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.