changeset 54262:e23ebe923867

8260967: Better jar file validation Reviewed-by: yan
author mbalao
date Mon, 29 Mar 2021 17:02:45 +0000
parents ab258233eeb2
children ca23657dc7da
files src/java.base/share/classes/java/util/jar/JarFile.java src/java.base/share/classes/java/util/jar/JarInputStream.java src/java.base/share/classes/java/util/jar/JarVerifier.java src/java.base/share/classes/java/util/zip/ZipFile.java src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java
diffstat 6 files changed, 75 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/util/jar/JarFile.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/java/util/jar/JarFile.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2021, 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
@@ -420,7 +420,13 @@
                 if (verify) {
                     byte[] b = getBytes(manEntry);
                     if (!jvInitialized) {
-                        jv = new JarVerifier(b);
+                        if (JUZFA.getManifestNum(this) == 1) {
+                            jv = new JarVerifier(manEntry.getName(), b);
+                        } else {
+                            if (JarVerifier.debug != null) {
+                                JarVerifier.debug.println("Multiple MANIFEST.MF found. Treat JAR file as unsigned");
+                            }
+                        }
                     }
                     man = new Manifest(jv, new ByteArrayInputStream(b));
                 } else {
--- a/src/java.base/share/classes/java/util/jar/JarInputStream.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/java/util/jar/JarInputStream.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2021, 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
@@ -95,7 +95,7 @@
             man.read(new ByteArrayInputStream(bytes));
             closeEntry();
             if (doVerify) {
-                jv = new JarVerifier(bytes);
+                jv = new JarVerifier(e.getName(), bytes);
                 mev = new ManifestEntryVerifier(man);
             }
             return (JarEntry)super.getNextEntry();
--- a/src/java.base/share/classes/java/util/jar/JarVerifier.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/java/util/jar/JarVerifier.java	Mon Mar 29 17:02:45 2021 +0000
@@ -84,6 +84,9 @@
     /** the bytes for the manDig object */
     byte manifestRawBytes[] = null;
 
+    /** the manifest name this JarVerifier is created upon */
+    final String manifestName;
+
     /** controls eager signature validation */
     boolean eagerValidation;
 
@@ -93,7 +96,8 @@
     /** collect -DIGEST-MANIFEST values for blacklist */
     private List<Object> manifestDigests;
 
-    public JarVerifier(byte rawBytes[]) {
+    public JarVerifier(String name, byte rawBytes[]) {
+        manifestName = name;
         manifestRawBytes = rawBytes;
         sigFileSigners = new Hashtable<>();
         verifiedSigners = new Hashtable<>();
@@ -180,7 +184,7 @@
 
         // only set the jev object for entries that have a signature
         // (either verified or not)
-        if (!name.equals(JarFile.MANIFEST_NAME)) {
+        if (!name.equalsIgnoreCase(JarFile.MANIFEST_NAME)) {
             if (sigFileSigners.get(name) != null ||
                     verifiedSigners.get(name) != null) {
                 mev.setEntry(name, je);
@@ -270,7 +274,8 @@
                             }
 
                             sfv.setSignatureFile(bytes);
-                            sfv.process(sigFileSigners, manifestDigests);
+                            sfv.process(sigFileSigners, manifestDigests,
+                                    manifestName);
                         }
                     }
                     return;
@@ -313,7 +318,7 @@
                         sfv.setSignatureFile(bytes);
                     }
                 }
-                sfv.process(sigFileSigners, manifestDigests);
+                sfv.process(sigFileSigners, manifestDigests, manifestName);
 
             } catch (IOException | CertificateException |
                     NoSuchAlgorithmException | SignatureException e) {
@@ -419,9 +424,9 @@
         manDig = null;
         // MANIFEST.MF is always treated as signed and verified,
         // move its signers from sigFileSigners to verifiedSigners.
-        if (sigFileSigners.containsKey(JarFile.MANIFEST_NAME)) {
-            CodeSigner[] codeSigners = sigFileSigners.remove(JarFile.MANIFEST_NAME);
-            verifiedSigners.put(JarFile.MANIFEST_NAME, codeSigners);
+        if (sigFileSigners.containsKey(manifestName)) {
+            CodeSigner[] codeSigners = sigFileSigners.remove(manifestName);
+            verifiedSigners.put(manifestName, codeSigners);
         }
     }
 
@@ -865,7 +870,7 @@
      */
     boolean isTrustedManifestEntry(String name) {
         // How many signers? MANIFEST.MF is always verified
-        CodeSigner[] forMan = verifiedSigners.get(JarFile.MANIFEST_NAME);
+        CodeSigner[] forMan = verifiedSigners.get(manifestName);
         if (forMan == null) {
             return true;
         }
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2021, 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
@@ -1083,6 +1083,18 @@
     }
 
     /**
+     * Returns the number of the META-INF/MANIFEST.MF entries, case insensitive.
+     * When this number is greater than 1, JarVerifier will treat a file as
+     * unsigned.
+     */
+    private int getManifestNum() {
+        synchronized (this) {
+            ensureOpen();
+            return res.zsrc.manifestNum;
+        }
+    }
+
+    /**
      * Returns the names of all non-directory entries that begin with
      * "META-INF/" (case ignored). This method is used in JarFile, via
      * SharedSecrets, as an optimization when looking up manifest and
@@ -1117,6 +1129,10 @@
                     return zip.res.zsrc.startsWithLoc;
                 }
                 @Override
+                public int getManifestNum(JarFile jar) {
+                    return ((ZipFile)jar).getManifestNum();
+                }
+                @Override
                 public String[] getMetaInfEntryNames(ZipFile zip) {
                     return zip.getMetaInfEntryNames();
                 }
@@ -1155,6 +1171,8 @@
     }
 
     private static class Source {
+        // "META-INF/".length()
+        private static final int META_INF_LEN = 9;
         private final Key key;               // the key in files
         private int refs = 1;
 
@@ -1164,6 +1182,7 @@
         private byte[] comment;              // zip file comment
                                              // list of meta entries in META-INF dir
         private int[] metanames;
+        private int   manifestNum = 0;       // number of META-INF/MANIFEST.MF, case insensitive
         private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)
 
         // A Hashmap for all entries.
@@ -1304,6 +1323,7 @@
             entries = null;
             table = null;
             metanames = null;
+            manifestNum = 0;
         }
 
         private static final int BUF_SIZE = 8192;
@@ -1523,6 +1543,7 @@
             int hsh;
             int pos = 0;
             int limit = cen.length - ENDHDR;
+            manifestNum = 0;
             while (pos + CENHDR <= limit) {
                 if (i >= total) {
                     // This will only happen if the zip file has an incorrect
@@ -1560,6 +1581,10 @@
                     if (metanamesList == null)
                         metanamesList = new ArrayList<>(4);
                     metanamesList.add(pos);
+                    if (isManifestName(cen, pos + CENHDR +
+                            META_INF_LEN, nlen - META_INF_LEN)) {
+                        manifestNum++;
+                    }
                 }
                 // skip ext and comment
                 pos += (CENHDR + nlen + elen + clen);
@@ -1655,6 +1680,24 @@
                 && (name[off]         ) == '/';
         }
 
+        /*
+         * Check if the bytes represents a name equals to MANIFEST.MF
+         */
+        private boolean isManifestName(byte[] name, int off, int len) {
+            return (len == 11 // "MANIFEST.MF".length()
+                    && (name[off++] | 0x20) == 'm'
+                    && (name[off++] | 0x20) == 'a'
+                    && (name[off++] | 0x20) == 'n'
+                    && (name[off++] | 0x20) == 'i'
+                    && (name[off++] | 0x20) == 'f'
+                    && (name[off++] | 0x20) == 'e'
+                    && (name[off++] | 0x20) == 's'
+                    && (name[off++] | 0x20) == 't'
+                    && (name[off++]       ) == '.'
+                    && (name[off++] | 0x20) == 'm'
+                    && (name[off]   | 0x20) == 'f');
+        }
+
         /**
          * Returns the number of CEN headers in a central directory.
          * Will not throw, even if the zip file is corrupt.
--- a/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaUtilZipFileAccess.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2021, 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
@@ -29,12 +29,14 @@
 import java.util.Enumeration;
 import java.util.function.Function;
 import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public interface JavaUtilZipFileAccess {
     public boolean startsWithLocHeader(ZipFile zip);
+    public int getManifestNum(JarFile zip);
     public String[] getMetaInfEntryNames(ZipFile zip);
     public JarEntry getEntry(ZipFile zip, String name, Function<String, JarEntry> func);
     public Enumeration<JarEntry> entries(ZipFile zip, Function<String, JarEntry> func);
--- a/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java	Mon Jun 07 15:35:39 2021 +0000
+++ b/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2021, 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
@@ -247,7 +247,7 @@
      *
      */
     public void process(Hashtable<String, CodeSigner[]> signers,
-            List<Object> manifestDigests)
+            List<Object> manifestDigests, String manifestName)
         throws IOException, SignatureException, NoSuchAlgorithmException,
             JarException, CertificateException
     {
@@ -256,7 +256,7 @@
         Object obj = null;
         try {
             obj = Providers.startJarVerification();
-            processImpl(signers, manifestDigests);
+            processImpl(signers, manifestDigests, manifestName);
         } finally {
             Providers.stopJarVerification(obj);
         }
@@ -264,7 +264,7 @@
     }
 
     private void processImpl(Hashtable<String, CodeSigner[]> signers,
-            List<Object> manifestDigests)
+            List<Object> manifestDigests, String manifestName)
         throws IOException, SignatureException, NoSuchAlgorithmException,
             JarException, CertificateException
     {
@@ -345,7 +345,7 @@
         }
 
         // MANIFEST.MF is always regarded as signed
-        updateSigners(newSigners, signers, JarFile.MANIFEST_NAME);
+        updateSigners(newSigners, signers, manifestName);
     }
 
     /**