8194534: Manifest better support
authorweijun
Tue, 17 Apr 2018 15:55:49 +0800
changeset 52159 42244a052fbb
parent 52158 f351c1a6c37a
child 52160 2de3d2f1df39
8194534: Manifest better support Reviewed-by: mchung, igerasim
src/java.base/share/classes/java/net/URLClassLoader.java
src/java.base/share/classes/java/util/jar/JarFile.java
src/java.base/share/classes/java/util/jar/JarVerifier.java
src/java.base/share/classes/java/util/jar/JavaUtilJarAccessImpl.java
src/java.base/share/classes/java/util/jar/Manifest.java
src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java
src/java.base/share/classes/jdk/internal/misc/JavaUtilJarAccess.java
test/lib/jdk/test/lib/util/JarUtils.java
--- a/src/java.base/share/classes/java/net/URLClassLoader.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/java/net/URLClassLoader.java	Tue Apr 17 15:55:49 2018 +0800
@@ -535,13 +535,13 @@
      * @spec JPMS
      */
     protected Package definePackage(String name, Manifest man, URL url) {
-        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);
@@ -585,10 +585,12 @@
     /*
      * 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);
--- a/src/java.base/share/classes/java/util/jar/JarFile.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/java/util/jar/JarFile.java	Tue Apr 17 15:55:49 2018 +0800
@@ -417,10 +417,10 @@
             if (manEntry != null) {
                 if (verify) {
                     byte[] b = getBytes(manEntry);
-                    man = new Manifest(new ByteArrayInputStream(b), getName());
                     if (!jvInitialized) {
                         jv = new JarVerifier(b);
                     }
+                    man = new Manifest(jv, new ByteArrayInputStream(b), getName());
                 } else {
                     man = new Manifest(super.getInputStream(manEntry), getName());
                 }
--- a/src/java.base/share/classes/java/util/jar/JarVerifier.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/java/util/jar/JarVerifier.java	Tue Apr 17 15:55:49 2018 +0800
@@ -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
@@ -866,4 +866,24 @@
     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;
+    }
 }
--- a/src/java.base/share/classes/java/util/jar/JavaUtilJarAccessImpl.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/java/util/jar/JavaUtilJarAccessImpl.java	Tue Apr 17 15:55:49 2018 +0800
@@ -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 @@
     public List<Object> getManifestDigests(JarFile jar) {
         return jar.getManifestDigests();
     }
+
+    public Attributes getTrustedAttributes(Manifest man, String name) {
+        return man.getTrustedAttributes(name);
+    }
+
 }
--- a/src/java.base/share/classes/java/util/jar/Manifest.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/java/util/jar/Manifest.java	Tue Apr 17 15:55:49 2018 +0800
@@ -50,10 +50,13 @@
 public class Manifest implements Cloneable {
 
     // manifest main attributes
-    private Attributes attr = new Attributes();
+    private final Attributes attr = new Attributes();
 
     // manifest entries
-    private Map<String, Attributes> entries = new HashMap<>();
+    private final Map<String, Attributes> entries = new HashMap<>();
+
+    // associated JarVerifier, not null when called by JarFile::getManifest.
+    private final JarVerifier jv;
 
     // name of the corresponding jar archive if available.
     private final String jarFilename;
@@ -63,6 +66,7 @@
      */
     public Manifest() {
         jarFilename = null;
+        jv = null;
     }
 
     /**
@@ -72,8 +76,7 @@
      * @throws IOException if an I/O error has occurred
      */
     public Manifest(InputStream is) throws IOException {
-        this();
-        read(is);
+        this(null, is, null);
     }
 
     /**
@@ -84,8 +87,17 @@
      * @throws IOException if an I/O error has occured
      */
     Manifest(InputStream is, String jarFilename) throws IOException {
+        this(null, is, jarFilename);
+    }
+
+    /**
+     * Constructs a new Manifest from the specified input stream
+     * and associates it with a JarVerifier.
+     */
+    Manifest(JarVerifier jv, InputStream is, String jarFilename) throws IOException {
         read(is);
         this.jarFilename = jarFilename;
+        this.jv = jv;
     }
 
     /**
@@ -94,9 +106,10 @@
      * @param man the Manifest to copy
      */
     public Manifest(Manifest man) {
-        this();
         attr.putAll(man.getMainAttributes());
         entries.putAll(man.getEntries());
+        jarFilename = null;
+        jv = man.jv;
     }
 
     /**
@@ -147,6 +160,23 @@
     }
 
     /**
+     * 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.
      */
     public void clear() {
--- a/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java	Tue Apr 17 15:55:49 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, 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
@@ -60,6 +60,7 @@
 import java.util.jar.Manifest;
 import java.util.stream.Stream;
 
+import jdk.internal.misc.SharedSecrets;
 import jdk.internal.misc.VM;
 import jdk.internal.module.ModulePatcher.PatchedModuleReader;
 import jdk.internal.module.Resources;
@@ -862,7 +863,8 @@
      * Manifest are used to get the package version and sealing information.
      *
      * @throws IllegalArgumentException if the package name duplicates an
-     * existing package either in this class loader or one of its ancestors
+     *      existing package either in this class loader or one of its ancestors
+     * @throws SecurityException if the package name is untrusted in the manifest
      */
     private Package definePackage(String pn, Manifest man, URL url) {
         String specTitle = null;
@@ -875,7 +877,8 @@
         URL sealBase = null;
 
         if (man != null) {
-            Attributes attr = man.getAttributes(pn.replace('.', '/').concat("/"));
+            Attributes attr = SharedSecrets.javaUtilJarAccess()
+                    .getTrustedAttributes(man, pn.replace('.', '/').concat("/"));
             if (attr != null) {
                 specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
                 specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
@@ -921,10 +924,12 @@
     /**
      * Returns {@code 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 pn, Manifest man) {
-        String path = pn.replace('.', '/').concat("/");
-        Attributes attr = man.getAttributes(path);
+        Attributes attr = SharedSecrets.javaUtilJarAccess()
+                .getTrustedAttributes(man, pn.replace('.', '/').concat("/"));
         String sealed = null;
         if (attr != null)
             sealed = attr.getValue(Attributes.Name.SEALED);
--- a/src/java.base/share/classes/jdk/internal/misc/JavaUtilJarAccess.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaUtilJarAccess.java	Tue Apr 17 15:55:49 2018 +0800
@@ -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.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 Enumeration<JarEntry> entries2(JarFile jar);
     public void setEagerValidation(JarFile jar, boolean eager);
     public List<Object> getManifestDigests(JarFile jar);
+    public Attributes getTrustedAttributes(Manifest man, String name);
 }
--- a/test/lib/jdk/test/lib/util/JarUtils.java	Wed Apr 04 13:55:30 2018 -0700
+++ b/test/lib/jdk/test/lib/util/JarUtils.java	Tue Apr 17 15:55:49 2018 +0800
@@ -23,6 +23,7 @@
 
 package jdk.test.lib.util;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -271,6 +272,11 @@
         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))) {
 
@@ -298,6 +304,22 @@
         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);
+        updateJar(src, dest, Map.of(JarFile.MANIFEST_NAME, bout.toByteArray()));
+    }
+
     private static void updateEntry(JarOutputStream jos, String name, Object content)
            throws IOException {
         if (content instanceof Boolean) {