diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1dea64eef..f83fbb10a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -364,6 +364,7 @@ Tom May Tomasz Bech Trejkaz Xaoza Ulrich Schmidt +Uwe Schindler Valentino Miazzo Victor Toni Vimil Saju diff --git a/WHATSNEW b/WHATSNEW index 1ba308409..592e7d8f0 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -23,6 +23,15 @@ Fixed bugs: Other changes: -------------- + * will now post-process the generated in order to mitigate + the frame injection attack possible in javadocs generated by Oracle + JDKs prior to Java7 Update 25. The vulnerability is known as + CVE-2013-1571. + There is an option to turn off the post-processing but it is only + recommended you do so if all your builds use a JDK that's not + vulnerable. + Bugzilla Report 55132. + Changes from Ant 1.9.0 TO Ant 1.9.1 =================================== diff --git a/contributors.xml b/contributors.xml index 4399cedb8..079cb2dc5 100644 --- a/contributors.xml +++ b/contributors.xml @@ -1460,6 +1460,10 @@ Ulrich Schmidt + + Uwe + Schindler + Valentino Miazzo diff --git a/manual/Tasks/javadoc.html b/manual/Tasks/javadoc.html index 1afd1a73b..164adeb0e 100644 --- a/manual/Tasks/javadoc.html +++ b/manual/Tasks/javadoc.html @@ -509,6 +509,18 @@ to <javadoc> using classpath, classpathref attributes or 1.4 No + + postProcessGeneratedJavadocs + Whether to post-process the generated javadocs in + order to mitigate CVE-2013-1571. Defaults to true. Since Ant + 1.9.2
+ There is a frame injection attack possible in javadocs generated by Oracle + JDKs prior to Java7 Update 25. When this flag is set to true, Ant + will check whether the docs are vulnerable and will try to fix them. + + 1.4 + No +

Format of the group attribute

diff --git a/src/main/org/apache/tools/ant/taskdefs/Javadoc.java b/src/main/org/apache/tools/ant/taskdefs/Javadoc.java index 9190eb4c1..b2d2093f3 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Javadoc.java +++ b/src/main/org/apache/tools/ant/taskdefs/Javadoc.java @@ -17,13 +17,20 @@ */ package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; -import java.io.BufferedWriter; -import java.io.BufferedReader; -import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -50,6 +57,7 @@ import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.ResourceCollection; import org.apache.tools.ant.types.resources.FileProvider; import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; import org.apache.tools.ant.util.JavaEnvUtils; /** @@ -81,6 +89,9 @@ public class Javadoc extends Task { private static final boolean JAVADOC_5 = !JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_4); + private static final String LOAD_FRAME = "function loadFrames() {"; + private static final int LOAD_FRAME_LEN = LOAD_FRAME.length(); + /** * Inner class used to manage doclet parameters. */ @@ -452,6 +463,8 @@ public class Javadoc extends Task { private String executable = null; private boolean docFilesSubDirs = false; private String excludeDocFilesSubDir = null; + private String docEncoding = null; + private boolean postProcessGeneratedJavadocs = true; private ResourceCollectionContainer nestedSourceFiles = new ResourceCollectionContainer(); @@ -1151,6 +1164,7 @@ public class Javadoc extends Task { public void setDocencoding(String enc) { cmd.createArgument().setValue("-docencoding"); cmd.createArgument().setValue(enc); + docEncoding = enc; } /** @@ -1655,6 +1669,14 @@ public class Javadoc extends Task { excludeDocFilesSubDir = s; } + /** + * Whether to post-process the generated javadocs in order to mitigate CVE-2013-1571. + * @since Ant 1.9.2 + */ + public void setPostProcessGeneratedJavadocs(boolean b) { + postProcessGeneratedJavadocs = b; + } + /** * Execute the task. * @throws BuildException on error @@ -1765,6 +1787,7 @@ public class Javadoc extends Task { throw new BuildException("Javadoc returned " + ret, getLocation()); } + postProcessGeneratedJavadocs(); } catch (IOException e) { throw new BuildException("Javadoc failed: " + e, e, getLocation()); } finally { @@ -2420,6 +2443,88 @@ public class Javadoc extends Task { } } + private void postProcessGeneratedJavadocs() throws IOException { + if (!postProcessGeneratedJavadocs) { + return; + } + final String fixData; + final InputStream in = getClass() + .getResourceAsStream("javadoc-frame-injections-fix.txt"); + if (in == null) { + throw new FileNotFoundException("Missing resource " + + "'javadoc-frame-injections-fix.txt' in " + + "classpath."); + } + try { + fixData = FileUtils.readFully(new InputStreamReader(in, "US-ASCII")).trim() + .replace("\r\n", StringUtils.LINE_SEP) + .replace("\n", StringUtils.LINE_SEP); + } finally { + FileUtils.close(in); + } + + final DirectoryScanner ds = new DirectoryScanner(); + ds.setBasedir(destDir); + ds.setCaseSensitive(false); + ds.setIncludes(new String[] { + "**/index.html", "**/index.htm", "**/toc.html", "**/toc.htm" + }); + ds.addDefaultExcludes(); + ds.scan(); + int patched = 0; + for (String f : ds.getIncludedFiles()) { + patched += postProcess(new File(destDir, f), fixData); + } + if (patched > 0) { + log("Patched " + patched + " link injection vulnerable javadocs", + Project.MSG_INFO); + } + } + + private int postProcess(File file, String fixData) throws IOException { + String enc = docEncoding != null ? docEncoding + : FILE_UTILS.getDefaultEncoding(); + // we load the whole file as one String (toc/index files are + // generally small, because they only contain frameset declaration): + InputStream fin = new FileInputStream(file); + String fileContents; + try { + fileContents = + FileUtils.safeReadFully(new InputStreamReader(fin, enc)); + } finally { + FileUtils.close(fin); + } + + // check if file may be vulnerable because it was not + // patched with "validURL(url)": + if (fileContents.indexOf("function validURL(url) {") < 0) { + // we need to patch the file! + String patchedFileContents = patchContent(fileContents, fixData); + if (!patchedFileContents.equals(fileContents)) { + FileOutputStream fos = new FileOutputStream(file); + try { + OutputStreamWriter w = new OutputStreamWriter(fos, enc); + w.write(patchedFileContents); + w.close(); + return 1; + } finally { + FileUtils.close(fos); + } + } + } + return 0; + } + + private String patchContent(String fileContents, String fixData) { + // using regexes here looks like overkill + int start = fileContents.indexOf(LOAD_FRAME); + if (start >= 0) { + return fileContents.substring(0, start) + fixData + + fileContents.substring(start + LOAD_FRAME_LEN); + } + return fileContents; + } + private class JavadocOutputStream extends LogOutputStream { JavadocOutputStream(int level) { super(Javadoc.this, level); diff --git a/src/resources/org/apache/tools/ant/taskdefs/javadoc-frame-injections-fix.txt b/src/resources/org/apache/tools/ant/taskdefs/javadoc-frame-injections-fix.txt new file mode 100644 index 000000000..1f8c97a60 --- /dev/null +++ b/src/resources/org/apache/tools/ant/taskdefs/javadoc-frame-injections-fix.txt @@ -0,0 +1,37 @@ + if (targetPage != "" && !validURL(targetPage)) + targetPage = "undefined"; + function validURL(url) { + var pos = url.indexOf(".html"); + if (pos == -1 || pos != url.length - 5) + return false; + var allowNumber = false; + var allowSep = false; + var seenDot = false; + for (var i = 0; i < url.length - 5; i++) { + var ch = url.charAt(i); + if ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + ch == '$' || + ch == '_') { + allowNumber = true; + allowSep = true; + } else if ('0' <= ch && ch <= '9' + || ch == '-') { + if (!allowNumber) + return false; + } else if (ch == '/' || ch == '.') { + if (!allowSep) + return false; + allowNumber = false; + allowSep = false; + if (ch == '.') + seenDot = true; + if (ch == '/' && seenDot) + return false; + } else { + return false; + } + } + return true; + } + function loadFrames() {