changeset 14488:465a2d03f9b0

8260967: Better jar file validation Reviewed-by: bae, andrew
author mbalao
date Mon, 29 Mar 2021 17:02:45 +0000
parents cd1f22f0c2a3
children f856b833b9cc
files make/mapfiles/libzip/mapfile-vers make/mapfiles/libzip/reorder-sparc make/mapfiles/libzip/reorder-sparcv9 make/mapfiles/libzip/reorder-x86 src/share/classes/java/util/jar/JarFile.java src/share/classes/java/util/jar/JarInputStream.java src/share/classes/java/util/jar/JarVerifier.java src/share/classes/java/util/zip/ZipFile.java src/share/classes/sun/misc/JavaUtilZipFileAccess.java src/share/classes/sun/security/util/SignatureFileVerifier.java src/share/native/java/util/zip/ZipFile.c src/share/native/java/util/zip/zip_util.c src/share/native/java/util/zip/zip_util.h
diffstat 13 files changed, 95 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/make/mapfiles/libzip/mapfile-vers	Tue Mar 09 20:29:37 2021 +0000
+++ b/make/mapfiles/libzip/mapfile-vers	Mon Mar 29 17:02:45 2021 +0000
@@ -66,6 +66,7 @@
 		Java_java_util_zip_ZipFile_open;
 		Java_java_util_zip_ZipFile_read;
 		Java_java_util_zip_ZipFile_startsWithLOC;
+		Java_java_util_zip_ZipFile_getManifestNum;
 
 		ZIP_Close;
 		ZIP_CRC32;
--- a/make/mapfiles/libzip/reorder-sparc	Tue Mar 09 20:29:37 2021 +0000
+++ b/make/mapfiles/libzip/reorder-sparc	Mon Mar 29 17:02:45 2021 +0000
@@ -19,6 +19,7 @@
 text: .text%Java_java_util_zip_ZipFile_open;
 text: .text%Java_java_util_zip_ZipFile_getTotal;
 text: .text%Java_java_util_zip_ZipFile_startsWithLOC;
+text: .text%Java_java_util_zip_ZipFile_getManifestNum;
 text: .text%Java_java_util_zip_ZipFile_getEntry;
 text: .text%Java_java_util_zip_ZipFile_freeEntry;
 text: .text%Java_java_util_zip_ZipFile_getEntryTime;
--- a/make/mapfiles/libzip/reorder-sparcv9	Tue Mar 09 20:29:37 2021 +0000
+++ b/make/mapfiles/libzip/reorder-sparcv9	Mon Mar 29 17:02:45 2021 +0000
@@ -19,6 +19,7 @@
 text: .text%Java_java_util_zip_ZipFile_open;
 text: .text%Java_java_util_zip_ZipFile_getTotal;
 text: .text%Java_java_util_zip_ZipFile_startsWithLOC;
+text: .text%Java_java_util_zip_ZipFile_getManifestNum;
 text: .text%Java_java_util_zip_ZipFile_getEntry;
 text: .text%Java_java_util_zip_ZipFile_freeEntry;
 text: .text%Java_java_util_zip_ZipFile_getEntryTime;
--- a/make/mapfiles/libzip/reorder-x86	Tue Mar 09 20:29:37 2021 +0000
+++ b/make/mapfiles/libzip/reorder-x86	Mon Mar 29 17:02:45 2021 +0000
@@ -20,6 +20,7 @@
 text: .text%Java_java_util_zip_ZipFile_open;
 text: .text%Java_java_util_zip_ZipFile_getTotal;
 text: .text%Java_java_util_zip_ZipFile_startsWithLOC;
+text: .text%Java_java_util_zip_ZipFile_getManifestNum;
 text: .text%Java_java_util_zip_ZipFile_getEntry;
 text: .text%Java_java_util_zip_ZipFile_freeEntry;
 text: .text%Java_java_util_zip_ZipFile_getEntryTime;
--- a/src/share/classes/java/util/jar/JarFile.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/classes/java/util/jar/JarFile.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018, 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
@@ -85,6 +85,9 @@
         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
     }
 
+    private static final sun.misc.JavaUtilZipFileAccess JUZFA =
+            sun.misc.SharedSecrets.getJavaUtilZipFileAccess();
+
     /**
      * The JAR manifest file name.
      */
@@ -192,7 +195,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/share/classes/java/util/jar/JarInputStream.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/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/share/classes/java/util/jar/JarVerifier.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/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);
@@ -272,7 +276,8 @@
                             }
 
                             sfv.setSignatureFile(bytes);
-                            sfv.process(sigFileSigners, manifestDigests);
+                            sfv.process(sigFileSigners, manifestDigests,
+                                    manifestName);
                         }
                     }
                     return;
@@ -315,7 +320,7 @@
                         sfv.setSignatureFile(bytes);
                     }
                 }
-                sfv.process(sigFileSigners, manifestDigests);
+                sfv.process(sigFileSigners, manifestDigests, manifestName);
 
             } catch (IOException ioe) {
                 // e.g. sun.security.pkcs.ParsingException
@@ -430,9 +435,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);
         }
     }
 
@@ -886,7 +891,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/share/classes/java/util/zip/ZipFile.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/classes/java/util/zip/ZipFile.java	Mon Mar 29 17:02:45 2021 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2015, 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
@@ -42,6 +42,7 @@
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.WeakHashMap;
+import java.util.jar.JarFile;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
@@ -63,6 +64,7 @@
     private final int total;       // total number of entries
     private final boolean locsig;  // if zip file starts with LOCSIG (usually true)
     private volatile boolean closeRequested = false;
+    private int manifestNum = 0;       // number of META-INF/MANIFEST.MF, case insensitive
 
     private static final int STORED = ZipEntry.STORED;
     private static final int DEFLATED = ZipEntry.DEFLATED;
@@ -228,6 +230,7 @@
         this.name = name;
         this.total = getTotal(jzfile);
         this.locsig = startsWithLOC(jzfile);
+        this.manifestNum = getManifestNum(jzfile);
     }
 
     /**
@@ -800,6 +803,9 @@
                 public boolean startsWithLocHeader(ZipFile zip) {
                     return zip.startsWithLocHeader();
                 }
+                public int getManifestNum(JarFile jar) {
+                    return ((ZipFile)jar).getManifestNum();
+                }
              }
         );
     }
@@ -812,10 +818,23 @@
         return locsig;
     }
 
+    /*
+     * 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 manifestNum;
+        }
+    }
+
     private static native long open(String name, int mode, long lastModified,
                                     boolean usemmap) throws IOException;
     private static native int getTotal(long jzfile);
     private static native boolean startsWithLOC(long jzfile);
+    private static native int getManifestNum(long jzfile);
     private static native int read(long jzfile, long jzentry,
                                    long pos, byte[] b, int off, int len);
 
--- a/src/share/classes/sun/misc/JavaUtilZipFileAccess.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/classes/sun/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
@@ -25,9 +25,11 @@
 
 package sun.misc;
 
+import java.util.jar.JarFile;
 import java.util.zip.ZipFile;
 
 public interface JavaUtilZipFileAccess {
     public boolean startsWithLocHeader(ZipFile zip);
+    public int getManifestNum(JarFile zip);
 }
 
--- a/src/share/classes/sun/security/util/SignatureFileVerifier.java	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/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);
     }
 
     /**
--- a/src/share/native/java/util/zip/ZipFile.c	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/native/java/util/zip/ZipFile.c	Mon Mar 29 17:02:45 2021 +0000
@@ -147,6 +147,14 @@
     return zip->locsig;
 }
 
+JNIEXPORT jint JNICALL
+Java_java_util_zip_ZipFile_getManifestNum(JNIEnv *env, jclass cls, jlong zfile)
+{
+    jzfile *zip = jlong_to_ptr(zfile);
+
+    return zip->manifestNum;
+}
+
 JNIEXPORT void JNICALL
 Java_java_util_zip_ZipFile_close(JNIEnv *env, jclass cls, jlong zfile)
 {
--- a/src/share/native/java/util/zip/zip_util.c	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/native/java/util/zip/zip_util.c	Mon Mar 29 17:02:45 2021 +0000
@@ -74,6 +74,8 @@
 #define PATH_MAX 1024
 #endif
 
+#define META_INF_LEN 9 /* "META-INF/".length() */
+
 static jint INITIAL_META_COUNT = 2;   /* initial number of entries in meta name array */
 
 /*
@@ -444,6 +446,25 @@
 }
 
 /*
+ * Check if the bytes represents a name equals to MANIFEST.MF
+ */
+static int
+isManifestName(const char *name, int length)
+{
+    const char *s;
+    if (length != (int)sizeof("MANIFEST.MF") - 1)
+        return 0;
+    for (s = "MANIFEST.MF"; *s != '\0'; s++) {
+        char c = *name++;
+        // Avoid toupper; it's locale-dependent
+        if (c >= 'a' && c <= 'z') c += 'A' - 'a';
+        if (*s != c)
+            return 0;
+    }
+    return 1;
+}
+
+/*
  * Increases the capacity of zip->metanames.
  * Returns non-zero in case of allocation error.
  */
@@ -513,6 +534,7 @@
 {
     free(zip->entries); zip->entries = NULL;
     free(zip->table);   zip->table   = NULL;
+    zip->manifestNum = 0;
     freeMetaNames(zip);
 }
 
@@ -666,6 +688,8 @@
     for (j = 0; j < tablelen; j++)
         table[j] = ZIP_ENDCHAIN;
 
+    zip->manifestNum = 0;
+
     /* Iterate through the entries in the central directory */
     for (i = 0, cp = cenbuf; cp <= cenend - CENHDR; i++, cp += CENSIZE(cp)) {
         /* Following are unsigned 16-bit */
@@ -693,9 +717,12 @@
             ZIP_FORMAT_ERROR("invalid CEN header (bad header size)");
 
         /* if the entry is metadata add it to our metadata names */
-        if (isMetaName((char *)cp+CENHDR, nlen))
+        if (isMetaName((char *)cp+CENHDR, nlen)) {
+            if (isManifestName((char *)cp+CENHDR+META_INF_LEN, nlen-META_INF_LEN))
+                zip->manifestNum++;
             if (addMetaName(zip, (char *)cp+CENHDR, nlen) != 0)
                 goto Catch;
+        }
 
         /* Record the CEN offset and the name hash in our hash cell. */
         entries[i].cenpos = cenpos + (cp - cenbuf);
--- a/src/share/native/java/util/zip/zip_util.h	Tue Mar 09 20:29:37 2021 +0000
+++ b/src/share/native/java/util/zip/zip_util.h	Mon Mar 29 17:02:45 2021 +0000
@@ -229,6 +229,7 @@
     char **metanames;     /* array of meta names (may have null names) */
     jint metacurrent;     /* the next empty slot in metanames array */
     jint metacount;       /* number of slots in metanames array */
+    jint manifestNum;     /* number of META-INF/MANIFEST.MF, case insensitive */
     jlong lastModified;   /* last modified time */
     jlong locpos;         /* position of first LOC header (usually 0) */
 } jzfile;