Skip to content

Commit

Permalink
8244624: Improve handling of JarFile META-INF resources
Browse files Browse the repository at this point in the history
Reviewed-by: lancea, weijun, martin
  • Loading branch information
cl4es committed May 11, 2020
1 parent a06585a commit ceda308
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 93 deletions.
83 changes: 30 additions & 53 deletions src/java.base/share/classes/java/util/jar/JarFile.java
Expand Up @@ -715,21 +715,14 @@ private void maybeInstantiateVerifier() throws IOException {
}

if (verify) {
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String nameLower : names) {
String name = nameLower.toUpperCase(Locale.ENGLISH);
if (name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.endsWith(".SF")) {
// Assume since we found a signature-related file
// that the jar is signed and that we therefore
// need a JarVerifier and Manifest
getManifest();
return;
}
}
// Gets the manifest name, but only if there are
// signature-related files. If so we can assume
// that the jar is signed and that we therefore
// need a JarVerifier and Manifest
String name = JUZFA.getManifestName(this, true);
if (name != null) {
getManifest();
return;
}
// No signature-related files; don't instantiate a
// verifier
Expand All @@ -746,30 +739,24 @@ private void initializeVerifier() {

// Verify "META-INF/" entries...
try {
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String name : names) {
String uname = name.toUpperCase(Locale.ENGLISH);
if (MANIFEST_NAME.equals(uname)
|| SignatureFileVerifier.isBlockOrSF(uname)) {
JarEntry e = getJarEntry(name);
if (e == null) {
throw new JarException("corrupted jar file");
}
if (mev == null) {
mev = new ManifestEntryVerifier
(getManifestFromReference());
}
byte[] b = getBytes(e);
if (b != null && b.length > 0) {
jv.beginEntry(e, mev);
jv.update(b.length, b, 0, b.length, mev);
jv.update(-1, null, 0, 0, mev);
}
}
List<String> names = JUZFA.getManifestAndSignatureRelatedFiles(this);
for (String name : names) {
JarEntry e = getJarEntry(name);
if (e == null) {
throw new JarException("corrupted jar file");
}
if (mev == null) {
mev = new ManifestEntryVerifier
(getManifestFromReference());
}
byte[] b = getBytes(e);
if (b != null && b.length > 0) {
jv.beginEntry(e, mev);
jv.update(b.length, b, 0, b.length, mev);
jv.update(-1, null, 0, 0, mev);
}
}
} catch (IOException ex) {
} catch (IOException | IllegalArgumentException ex) {
// if we had an error parsing any blocks, just
// treat the jar file as being unsigned
jv = null;
Expand Down Expand Up @@ -935,22 +922,12 @@ private JarEntry verifiableEntry(ZipEntry ze) {

private JarEntry getManEntry() {
if (manEntry == null) {
// First look up manifest entry using standard name
JarEntry manEntry = getEntry0(MANIFEST_NAME);
if (manEntry == null) {
// If not found, then iterate through all the "META-INF/"
// entries to find a match.
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String name : names) {
if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
manEntry = getEntry0(name);
break;
}
}
}
// The manifest entry position is resolved during
// initialization
String name = JUZFA.getManifestName(this, false);
if (name != null) {
this.manEntry = getEntry0(name);
}
this.manEntry = manEntry;
}
return manEntry;
}
Expand Down Expand Up @@ -1213,7 +1190,7 @@ CodeSource getCodeSource(URL url, String name) {
ensureInitialization();
if (jv != null) {
if (jv.eagerValidation) {
CodeSource cs = null;
CodeSource cs;
JarEntry je = getJarEntry(name);
if (je != null) {
cs = jv.getCodeSource(url, this, je);
Expand Down
163 changes: 124 additions & 39 deletions src/java.base/share/classes/java/util/zip/ZipFile.java
Expand Up @@ -45,6 +45,8 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.NoSuchElementException;
import java.util.Set;
Expand All @@ -66,6 +68,7 @@
import jdk.internal.ref.CleanerFactory;
import jdk.internal.vm.annotation.Stable;
import sun.nio.cs.UTF_8;
import sun.security.util.SignatureFileVerifier;

import static java.util.zip.ZipConstants64.*;
import static java.util.zip.ZipUtils.*;
Expand Down Expand Up @@ -1010,31 +1013,51 @@ public void close() {
}

/**
* 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
* signature file entries. Returns null if no entries were found.
* Returns the names of the META-INF/MANIFEST.MF entry - if exists -
* and any signature-related files under META-INF. This method is used in
* JarFile, via SharedSecrets, as an optimization.
*/
private String[] getMetaInfEntryNames() {
private List<String> getManifestAndSignatureRelatedFiles() {
synchronized (this) {
ensureOpen();
Source zsrc = res.zsrc;
if (zsrc.metanames == null) {
return null;
int[] metanames = zsrc.signatureMetaNames;
List<String> files = null;
if (zsrc.manifestPos >= 0) {
files = new ArrayList<>();
files.add(getEntryName(zsrc.manifestPos));
}
String[] names = new String[zsrc.metanames.length];
byte[] cen = zsrc.cen;
for (int i = 0; i < names.length; i++) {
int pos = zsrc.metanames[i];
// This will only be invoked on JarFile, which is guaranteed
// to use (or be compatible with) UTF-8 encoding.
names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos),
UTF_8.INSTANCE);
if (metanames != null) {
if (files == null) {
files = new ArrayList<>();
}
for (int i = 0; i < metanames.length; i++) {
files.add(getEntryName(metanames[i]));
}
}
return names;
return files == null ? List.of() : files;
}
}

/**
* Returns the name of the META-INF/MANIFEST.MF entry, ignoring
* case. If {@code onlyIfSignatureRelatedFiles} is true, we only return the
* manifest if there is also at least one signature-related file.
* This method is used in JarFile, via SharedSecrets, as an optimization
* when looking up the manifest file.
*/
private String getManifestName(boolean onlyIfSignatureRelatedFiles) {
synchronized (this) {
ensureOpen();
Source zsrc = res.zsrc;
int pos = zsrc.manifestPos;
if (pos >= 0 && (!onlyIfSignatureRelatedFiles || zsrc.signatureMetaNames != null)) {
return getEntryName(pos);
}
}
return null;
}

/**
* Returns the versions for which there exists a non-directory
* entry that begin with "META-INF/versions/" (case ignored).
Expand All @@ -1059,8 +1082,12 @@ public boolean startsWithLocHeader(ZipFile zip) {
return zip.res.zsrc.startsWithLoc;
}
@Override
public String[] getMetaInfEntryNames(JarFile jar) {
return ((ZipFile)jar).getMetaInfEntryNames();
public List<String> getManifestAndSignatureRelatedFiles(JarFile jar) {
return ((ZipFile)jar).getManifestAndSignatureRelatedFiles();
}
@Override
public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFiles) {
return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles);
}
@Override
public int[] getMetaInfVersions(JarFile jar) {
Expand Down Expand Up @@ -1105,7 +1132,8 @@ private static class Source {
private long locpos; // position of first LOC header (usually 0)
private byte[] comment; // zip file comment
// list of meta entries in META-INF dir
private int[] metanames;
private int manifestPos = -1; // position of the META-INF/MANIFEST.MF, if exists
private int[] signatureMetaNames; // positions of signature related entries, if such exist
private int[] metaVersions; // list of unique versions found in META-INF/versions/
private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)

Expand Down Expand Up @@ -1254,7 +1282,8 @@ private void close() throws IOException {
cen = null;
entries = null;
table = null;
metanames = null;
manifestPos = -1;
signatureMetaNames = null;
metaVersions = EMPTY_META_VERSIONS;
}

Expand Down Expand Up @@ -1438,7 +1467,7 @@ private void initCEN(int knownTotal) throws IOException {
int next;

// list for all meta entries
ArrayList<Integer> metanamesList = null;
ArrayList<Integer> signatureNames = null;
// Set of all version numbers seen in META-INF/versions/
Set<Integer> metaVersionsSet = null;

Expand Down Expand Up @@ -1476,19 +1505,27 @@ private void initCEN(int knownTotal) throws IOException {
idx = addEntry(idx, hash, next, pos);
// Adds name to metanames.
if (isMetaName(cen, entryPos, nlen)) {
if (metanamesList == null)
metanamesList = new ArrayList<>(4);
metanamesList.add(pos);

// If this is a versioned entry, parse the version
// and store it for later. This optimizes lookup
// performance in multi-release jar files
int version = getMetaVersion(cen,
entryPos + META_INF_LENGTH, nlen - META_INF_LENGTH);
if (version > 0) {
if (metaVersionsSet == null)
metaVersionsSet = new TreeSet<>();
metaVersionsSet.add(version);
// nlen is at least META_INF_LENGTH
if (isManifestName(cen, entryPos + META_INF_LENGTH,
nlen - META_INF_LENGTH)) {
manifestPos = pos;
} else {
if (isSignatureRelated(cen, entryPos, nlen)) {
if (signatureNames == null)
signatureNames = new ArrayList<>(4);
signatureNames.add(pos);
}

// If this is a versioned entry, parse the version
// and store it for later. This optimizes lookup
// performance in multi-release jar files
int version = getMetaVersion(cen,
entryPos + META_INF_LENGTH, nlen - META_INF_LENGTH);
if (version > 0) {
if (metaVersionsSet == null)
metaVersionsSet = new TreeSet<>();
metaVersionsSet.add(version);
}
}
}
// skip ext and comment
Expand All @@ -1497,10 +1534,11 @@ private void initCEN(int knownTotal) throws IOException {
i++;
}
total = i;
if (metanamesList != null) {
metanames = new int[metanamesList.size()];
for (int j = 0, len = metanames.length; j < len; j++) {
metanames[j] = metanamesList.get(j);
if (signatureNames != null) {
int len = signatureNames.size();
signatureMetaNames = new int[len];
for (int j = 0; j < len; j++) {
signatureMetaNames[j] = signatureNames.get(j);
}
}
if (metaVersionsSet != null) {
Expand Down Expand Up @@ -1580,7 +1618,8 @@ private ZipCoder zipCoderForPos(int pos) {
* beginning with "META-INF/", disregarding ASCII case.
*/
private static boolean isMetaName(byte[] name, int off, int len) {
// Use the "oldest ASCII trick in the book"
// Use the "oldest ASCII trick in the book":
// ch | 0x20 == Character.toLowerCase(ch)
return len > META_INF_LENGTH // "META-INF/".length()
&& name[off + len - 1] != '/' // non-directory
&& (name[off++] | 0x20) == 'm'
Expand All @@ -1594,6 +1633,52 @@ private static boolean isMetaName(byte[] name, int off, int len) {
&& (name[off] ) == '/';
}

/*
* Check if the bytes represents a name equals to MANIFEST.MF
*/
private static 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');
}

private static boolean isSignatureRelated(byte[] name, int off, int len) {
// Only called when isMetaName(name, off, len) is true, which means
// len is at least META_INF_LENGTH
// assert isMetaName(name, off, len)
boolean signatureRelated = false;
if (name[off + len - 3] == '.') {
// Check if entry ends with .EC and .SF
int b1 = name[off + len - 2] | 0x20;
int b2 = name[off + len - 1] | 0x20;
if ((b1 == 'e' && b2 == 'c') || (b1 == 's' && b2 == 'f')) {
signatureRelated = true;
}
} else if (name[off + len - 4] == '.') {
// Check if entry ends with .DSA and .RSA
int b1 = name[off + len - 3] | 0x20;
int b2 = name[off + len - 2] | 0x20;
int b3 = name[off + len - 1] | 0x20;
if ((b1 == 'r' || b1 == 'd') && b2 == 's' && b3 == 'a') {
signatureRelated = true;
}
}
// Above logic must match SignatureFileVerifier.isBlockOrSF
assert(signatureRelated == SignatureFileVerifier
.isBlockOrSF(new String(name, off, len, UTF_8.INSTANCE)
.toUpperCase(Locale.ENGLISH)));
return signatureRelated;
}

/*
* If the bytes represents a non-directory name beginning
* with "versions/", continuing with a positive integer,
Expand Down
Expand Up @@ -26,6 +26,7 @@
package jdk.internal.access;

import java.util.Enumeration;
import java.util.List;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
Expand All @@ -34,7 +35,8 @@

public interface JavaUtilZipFileAccess {
public boolean startsWithLocHeader(ZipFile zip);
public String[] getMetaInfEntryNames(JarFile zip);
public List<String> getManifestAndSignatureRelatedFiles(JarFile zip);
public String getManifestName(JarFile zip, boolean onlyIfSignatureRelatedFiles);
public int[] getMetaInfVersions(JarFile zip);
public JarEntry getEntry(ZipFile zip, String name, Function<String, JarEntry> func);
public Enumeration<JarEntry> entries(ZipFile zip, Function<String, JarEntry> func);
Expand Down
Expand Up @@ -175,6 +175,7 @@ public void setSignatureFile(byte[] sfBytes)
* Signature File or PKCS7 block file name
*/
public static boolean isBlockOrSF(String s) {
// Note: keep this in sync with j.u.z.ZipFile.Source#isSignatureRelated
// we currently only support DSA and RSA PKCS7 blocks
return s.endsWith(".SF")
|| s.endsWith(".DSA")
Expand Down

0 comments on commit ceda308

Please sign in to comment.