From c270cf4cf35afb09a63fa4bd32269daae4ab3c58 Mon Sep 17 00:00:00 2001 From: igerasim Date: Wed, 18 Apr 2018 22:35:30 -0700 Subject: [PATCH] 8194534: Manifest better support Reviewed-by: mchung, igerasim --- .../classes/java/net/URLClassLoader.java | 13 +- src/share/classes/java/util/jar/JarFile.java | 4 +- .../classes/java/util/jar/JarVerifier.java | 22 ++- .../java/util/jar/JavaUtilJarAccessImpl.java | 7 +- src/share/classes/java/util/jar/Manifest.java | 37 ++++- .../classes/sun/misc/JavaUtilJarAccess.java | 5 +- .../jdk/testlibrary/InMemoryJavaCompiler.java | 154 ++++++++++++++++++ .../testlibrary/jdk/testlibrary/JarUtils.java | 26 ++- 8 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 test/lib/testlibrary/jdk/testlibrary/InMemoryJavaCompiler.java diff --git a/src/share/classes/java/net/URLClassLoader.java b/src/share/classes/java/net/URLClassLoader.java index c2394c70b..416941c79 100644 --- a/src/share/classes/java/net/URLClassLoader.java +++ b/src/share/classes/java/net/URLClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,6 +50,7 @@ import java.util.jar.Attributes.Name; import java.util.jar.JarFile; import java.util.jar.Manifest; import sun.misc.Resource; +import sun.misc.SharedSecrets; import sun.misc.URLClassPath; import sun.net.www.ParseUtil; import sun.security.util.SecurityConstants; @@ -486,13 +487,13 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { - String path = name.replace('.', '/').concat("/"); String specTitle = null, specVersion = null, specVendor = null; String implTitle = null, implVersion = null, implVendor = null; String sealed = null; URL sealBase = null; - Attributes attr = man.getAttributes(path); + Attributes attr = SharedSecrets.javaUtilJarAccess() + .getTrustedAttributes(man, name.replace('.', '/').concat("/")); if (attr != null) { specTitle = attr.getValue(Name.SPECIFICATION_TITLE); specVersion = attr.getValue(Name.SPECIFICATION_VERSION); @@ -536,10 +537,12 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { /* * Returns true if the specified package name is sealed according to the * given manifest. + * + * @throws SecurityException if the package name is untrusted in the manifest */ private boolean isSealed(String name, Manifest man) { - String path = name.replace('.', '/').concat("/"); - Attributes attr = man.getAttributes(path); + Attributes attr = SharedSecrets.javaUtilJarAccess() + .getTrustedAttributes(man, name.replace('.', '/').concat("/")); String sealed = null; if (attr != null) { sealed = attr.getValue(Name.SEALED); diff --git a/src/share/classes/java/util/jar/JarFile.java b/src/share/classes/java/util/jar/JarFile.java index f78c36c61..e68b92b6a 100644 --- a/src/share/classes/java/util/jar/JarFile.java +++ b/src/share/classes/java/util/jar/JarFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -191,10 +191,10 @@ class JarFile extends ZipFile { if (manEntry != null) { if (verify) { byte[] b = getBytes(manEntry); - man = new Manifest(new ByteArrayInputStream(b)); if (!jvInitialized) { jv = new JarVerifier(b); } + man = new Manifest(jv, new ByteArrayInputStream(b)); } else { man = new Manifest(super.getInputStream(manEntry)); } diff --git a/src/share/classes/java/util/jar/JarVerifier.java b/src/share/classes/java/util/jar/JarVerifier.java index 6e6eec553..34bf00c75 100644 --- a/src/share/classes/java/util/jar/JarVerifier.java +++ b/src/share/classes/java/util/jar/JarVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -879,4 +879,24 @@ class JarVerifier { static CodeSource getUnsignedCS(URL url) { return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null); } + + /** + * Returns whether the name is trusted. Used by + * {@link Manifest#getTrustedAttributes(String)}. + */ + boolean isTrustedManifestEntry(String name) { + // How many signers? MANIFEST.MF is always verified + CodeSigner[] forMan = verifiedSigners.get(JarFile.MANIFEST_NAME); + if (forMan == null) { + return true; + } + // Check sigFileSigners first, because we are mainly dealing with + // non-file entries which will stay in sigFileSigners forever. + CodeSigner[] forName = sigFileSigners.get(name); + if (forName == null) { + forName = verifiedSigners.get(name); + } + // Returns trusted if all signers sign the entry + return forName != null && forName.length == forMan.length; + } } diff --git a/src/share/classes/java/util/jar/JavaUtilJarAccessImpl.java b/src/share/classes/java/util/jar/JavaUtilJarAccessImpl.java index 84a72797f..aca00dbcb 100644 --- a/src/share/classes/java/util/jar/JavaUtilJarAccessImpl.java +++ b/src/share/classes/java/util/jar/JavaUtilJarAccessImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,4 +60,9 @@ class JavaUtilJarAccessImpl implements JavaUtilJarAccess { public List getManifestDigests(JarFile jar) { return jar.getManifestDigests(); } + + public Attributes getTrustedAttributes(Manifest man, String name) { + return man.getTrustedAttributes(name); + } + } diff --git a/src/share/classes/java/util/jar/Manifest.java b/src/share/classes/java/util/jar/Manifest.java index 976e44c53..66310d15e 100644 --- a/src/share/classes/java/util/jar/Manifest.java +++ b/src/share/classes/java/util/jar/Manifest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,15 +48,19 @@ import java.util.Iterator; */ public class Manifest implements Cloneable { // manifest main attributes - private Attributes attr = new Attributes(); + private final Attributes attr = new Attributes(); // manifest entries - private Map entries = new HashMap<>(); + private final Map entries = new HashMap<>(); + + // associated JarVerifier, not null when called by JarFile::getManifest. + private final JarVerifier jv; /** * Constructs a new, empty Manifest. */ public Manifest() { + jv = null; } /** @@ -66,7 +70,16 @@ public class Manifest implements Cloneable { * @throws IOException if an I/O error has occurred */ public Manifest(InputStream is) throws IOException { + this(null, is); + } + + /** + * Constructs a new Manifest from the specified input stream + * and associates it with a JarVerifier. + */ + Manifest(JarVerifier jv, InputStream is) throws IOException { read(is); + this.jv = jv; } /** @@ -77,6 +90,7 @@ public class Manifest implements Cloneable { public Manifest(Manifest man) { attr.putAll(man.getMainAttributes()); entries.putAll(man.getEntries()); + jv = man.jv; } /** @@ -126,6 +140,23 @@ public class Manifest implements Cloneable { return getEntries().get(name); } + /** + * Returns the Attributes for the specified entry name, if trusted. + * + * @param name entry name + * @return returns the same result as {@link #getAttributes(String)} + * @throws SecurityException if the associated jar is signed but this entry + * has been modified after signing (i.e. the section in the manifest + * does not exist in SF files of all signers). + */ + Attributes getTrustedAttributes(String name) { + Attributes result = getAttributes(name); + if (result != null && jv != null && ! jv.isTrustedManifestEntry(name)) { + throw new SecurityException("Untrusted manifest entry: " + name); + } + return result; + } + /** * Clears the main Attributes as well as the entries in this Manifest. */ diff --git a/src/share/classes/sun/misc/JavaUtilJarAccess.java b/src/share/classes/sun/misc/JavaUtilJarAccess.java index 3453855d5..1d2e4ea86 100644 --- a/src/share/classes/sun/misc/JavaUtilJarAccess.java +++ b/src/share/classes/sun/misc/JavaUtilJarAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,8 +30,10 @@ import java.net.URL; import java.security.CodeSource; import java.util.Enumeration; import java.util.List; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.Manifest; public interface JavaUtilJarAccess { public boolean jarFileHasClassPathAttribute(JarFile jar) throws IOException; @@ -41,4 +43,5 @@ public interface JavaUtilJarAccess { public Enumeration entries2(JarFile jar); public void setEagerValidation(JarFile jar, boolean eager); public List getManifestDigests(JarFile jar); + public Attributes getTrustedAttributes(Manifest man, String name); } diff --git a/test/lib/testlibrary/jdk/testlibrary/InMemoryJavaCompiler.java b/test/lib/testlibrary/jdk/testlibrary/InMemoryJavaCompiler.java new file mode 100644 index 000000000..b07c1f202 --- /dev/null +++ b/test/lib/testlibrary/jdk/testlibrary/InMemoryJavaCompiler.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.testlibrary; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.URI; +import java.util.Arrays; + +import javax.tools.ForwardingJavaFileManager; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.FileObject; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +/** + * {@code InMemoryJavaCompiler} can be used for compiling a {@link + * CharSequence} to a {@code byte[]}. + * + * The compiler will not use the file system at all, instead using a {@link + * ByteArrayOutputStream} for storing the byte code. For the source code, any + * kind of {@link CharSequence} can be used, e.g. {@link String}, {@link + * StringBuffer} or {@link StringBuilder}. + * + * The {@code InMemoryCompiler} can easily be used together with a {@code + * ByteClassLoader} to easily compile and load source code in a {@link String}: + * + *
+ * {@code
+ * import com.oracle.java.testlibrary.InMemoryJavaCompiler;
+ * import com.oracle.java.testlibrary.ByteClassLoader;
+ *
+ * class Example {
+ *     public static void main(String[] args) {
+ *         String className = "Foo";
+ *         String sourceCode = "public class " + className + " {" +
+ *                             "    public void bar() {" +
+ *                             "        System.out.println("Hello from bar!");" +
+ *                             "    }" +
+ *                             "}";
+ *         byte[] byteCode = InMemoryJavaCompiler.compile(className, sourceCode);
+ *         Class fooClass = ByteClassLoader.load(className, byteCode);
+ *     }
+ * }
+ * }
+ * 
+ */ +public class InMemoryJavaCompiler { + private static class MemoryJavaFileObject extends SimpleJavaFileObject { + private final String className; + private final CharSequence sourceCode; + private final ByteArrayOutputStream byteCode; + + public MemoryJavaFileObject(String className, CharSequence sourceCode) { + super(URI.create("string:///" + className.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE); + this.className = className; + this.sourceCode = sourceCode; + this.byteCode = new ByteArrayOutputStream(); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return sourceCode; + } + + @Override + public OutputStream openOutputStream() throws IOException { + return byteCode; + } + + public byte[] getByteCode() { + return byteCode.toByteArray(); + } + + public String getClassName() { + return className; + } + } + + private static class FileManagerWrapper extends ForwardingJavaFileManager { + private MemoryJavaFileObject file; + + public FileManagerWrapper(MemoryJavaFileObject file) { + super(getCompiler().getStandardFileManager(null, null, null)); + this.file = file; + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, + Kind kind, FileObject sibling) + throws IOException { + if (!file.getClassName().equals(className)) { + throw new IOException("Expected class with name " + file.getClassName() + + ", but got " + className); + } + return file; + } + } + + /** + * Compiles the class with the given name and source code. + * + * @param className The name of the class + * @param sourceCode The source code for the class with name {@code className} + * @throws RuntimeException if the compilation did not succeed + * @return The resulting byte code from the compilation + */ + public static byte[] compile(String className, CharSequence sourceCode) { + MemoryJavaFileObject file = new MemoryJavaFileObject(className, sourceCode); + CompilationTask task = getCompilationTask(file); + + if(!task.call()) { + throw new RuntimeException("Could not compile " + className + " with source code " + sourceCode); + } + + return file.getByteCode(); + } + + private static JavaCompiler getCompiler() { + return ToolProvider.getSystemJavaCompiler(); + } + + private static CompilationTask getCompilationTask(MemoryJavaFileObject file) { + return getCompiler().getTask(null, new FileManagerWrapper(file), null, null, null, Arrays.asList(file)); + } +} diff --git a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java index 1be66e5e3..b8e9970c9 100644 --- a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java +++ b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ package jdk.testlibrary; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -126,6 +127,11 @@ public final class JarUtils { changes = new HashMap<>(changes); System.out.printf("Creating %s from %s...\n", dest, src); + + if (dest.equals(src)) { + throw new IOException("src and dest cannot be the same"); + } + try (JarOutputStream jos = new JarOutputStream( new FileOutputStream(dest))) { @@ -153,6 +159,24 @@ public final class JarUtils { System.out.println(); } + /** + * Update the Manifest inside a jar. + * + * @param src the original jar file name + * @param dest the new jar file name + * @param man the Manifest + * + * @throws IOException + */ + public static void updateManifest(String src, String dest, Manifest man) + throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + man.write(bout); + Map map = new HashMap<>(); + map.put(JarFile.MANIFEST_NAME, bout.toByteArray()); + updateJar(src, dest, map); + } + private static void updateEntry(JarOutputStream jos, String name, Object content) throws IOException { if (content instanceof Boolean) { -- GitLab