summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageManager.java11
-rw-r--r--core/java/android/content/pm/PackageParser.java596
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTests.java6
-rw-r--r--packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java24
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java33
6 files changed, 360 insertions, 323 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8d9b8d9e0ade..5d55b0a11bd4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2870,15 +2870,12 @@ public abstract class PackageManager {
*
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
- PackageParser packageParser = new PackageParser(archiveFilePath);
- DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
- final File sourceFile = new File(archiveFilePath);
+ final PackageParser parser = new PackageParser();
+ final File apkFile = new File(archiveFilePath);
try {
- PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, metrics,
- 0);
+ PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
- packageParser.collectCertificates(pkg, 0);
+ parser.collectCertificates(pkg, 0);
}
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 618c2bd646ff..c8acd66f3b2d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,9 +16,14 @@
package android.content.pm;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
import android.content.ComponentName;
import android.content.Intent;
@@ -33,6 +38,8 @@ import android.os.Bundle;
import android.os.PatternMatcher;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Base64;
import android.util.DisplayMetrics;
@@ -41,12 +48,18 @@ import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
-import java.io.BufferedInputStream;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
@@ -58,21 +71,19 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
/**
* Package archive parsing
*
@@ -153,17 +164,21 @@ public class PackageParser {
android.os.Build.VERSION_CODES.JELLY_BEAN)
};
+ /**
+ * @deprecated callers should move to explicitly passing around source path.
+ */
+ @Deprecated
private String mArchiveSourcePath;
+
private String[] mSeparateProcesses;
private boolean mOnlyCoreApps;
+ private DisplayMetrics mMetrics;
+
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
private int mParseError = PackageManager.INSTALL_SUCCEEDED;
- private static final Object mSync = new Object();
- private static WeakReference<byte[]> mReadBuffer;
-
private static boolean sCompatibilityModeEnabled = true;
private static final int PARSE_DEFAULT_INSTALL_LOCATION =
PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
@@ -247,12 +262,9 @@ public class PackageParser {
private static final String TAG = "PackageParser";
- public PackageParser(String archiveSourcePath) {
- mArchiveSourcePath = archiveSourcePath;
- }
-
- public PackageParser(File archiveSource) {
- this(archiveSource.getAbsolutePath());
+ public PackageParser() {
+ mMetrics = new DisplayMetrics();
+ mMetrics.setToDefaults();
}
public void setSeparateProcesses(String[] procs) {
@@ -263,6 +275,10 @@ public class PackageParser {
mOnlyCoreApps = onlyCoreApps;
}
+ public void setDisplayMetrics(DisplayMetrics metrics) {
+ mMetrics = metrics;
+ }
+
private static final boolean isPackageFilename(File file) {
return isPackageFilename(file.getName());
}
@@ -480,23 +496,21 @@ public class PackageParser {
return pi;
}
- private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry je,
- byte[] readBuffer) {
+ private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
+ throws PackageParserException {
+ InputStream is = null;
try {
// We must read the stream for the JarEntry to retrieve
// its certificates.
- InputStream is = new BufferedInputStream(jarFile.getInputStream(je));
- while (is.read(readBuffer, 0, readBuffer.length) != -1) {
- // not using
- }
- is.close();
- return je != null ? jarFile.getCertificateChains(je) : null;
- } catch (IOException e) {
- Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception reading " + je.getName() + " in " + jarFile, e);
+ is = jarFile.getInputStream(entry);
+ readFullyIgnoringContents(is);
+ return jarFile.getCertificateChains(entry);
+ } catch (IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed reading " + entry.getName() + " in " + jarFile, e);
+ } finally {
+ IoUtils.closeQuietly(is);
}
- return null;
}
public final static int PARSE_IS_SYSTEM = 1<<0;
@@ -508,67 +522,116 @@ public class PackageParser {
public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
public final static int PARSE_IS_PRIVILEGED = 1<<7;
public final static int PARSE_GET_SIGNATURES = 1<<8;
+ public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
+
+ private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
/**
- * Parse all APK files under the given directory as a single package.
+ * Used to sort a set of APKs based on their split names, always placing the
+ * base APK (with {@code null} split name) first.
*/
- public Package parseSplitPackage(File apkDir, DisplayMetrics metrics, int flags,
- boolean trustedOverlay) throws PackageParserException {
+ private static class SplitNameComparator implements Comparator<String> {
+ @Override
+ public int compare(String lhs, String rhs) {
+ if (lhs == null) {
+ return -1;
+ } else if (rhs == null) {
+ return 1;
+ } else {
+ return lhs.compareTo(rhs);
+ }
+ }
+ }
+
+ /**
+ * Parse all APKs contained in the given directory, treating them as a
+ * single package. This also performs sanity checking, such as requiring
+ * identical package name and version codes, a single base APK, and unique
+ * split names.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #collectCertificates(Package, int)}.
+ */
+ public Package parseSplitPackage(File apkDir, int flags) throws PackageParserException {
final File[] files = apkDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"No packages found in split");
}
- File baseFile = null;
+ String packageName = null;
+ int versionCode = 0;
+
+ final ArrayMap<String, File> apks = new ArrayMap<>();
for (File file : files) {
if (file.isFile() && isPackageFilename(file)) {
- ApkLite lite = parseApkLite(file.getAbsolutePath(), 0);
- if (lite == null) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
- "Invalid package file: " + file);
+ final ApkLite lite = parseApkLite(file, 0);
+
+ // Assert that all package names and version codes are
+ // consistent with the first one we encounter.
+ if (packageName == null) {
+ packageName = lite.packageName;
+ versionCode = lite.versionCode;
+ } else {
+ if (!packageName.equals(lite.packageName)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent package " + lite.packageName + " in " + file
+ + "; expected " + packageName);
+ }
+ if (versionCode != lite.versionCode) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent version " + lite.versionCode + " in " + file
+ + "; expected " + versionCode);
+ }
}
- if (TextUtils.isEmpty(lite.splitName)) {
- baseFile = file;
- break;
+ // Assert that each split is defined only once
+ if (apks.put(lite.splitName, file) != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split name " + lite.splitName
+ + " defined more than once; most recent was " + file);
}
}
}
+ final File baseFile = apks.remove(null);
if (baseFile == null) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Missing base APK in " + apkDir);
}
- final Package pkg = parseBaseApk(baseFile, metrics, flags, trustedOverlay);
+ // Always apply deterministic ordering based on splitName
+ final int size = apks.size();
+
+ final String[] splitNames = apks.keySet().toArray(new String[size]);
+ Arrays.sort(splitNames, sSplitNameComparator);
+
+ final File[] splitFiles = new File[size];
+ for (int i = 0; i < size; i++) {
+ splitFiles[i] = apks.get(splitNames[i]);
+ }
+
+ final Package pkg = parseBaseApk(baseFile, flags);
if (pkg == null) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse base APK: " + baseFile);
}
- for (File file : files) {
- if (file.isFile() && isPackageFilename(file) && !file.equals(baseFile)) {
- parseSplitApk(pkg, file, metrics, flags, trustedOverlay);
- }
- }
-
- // Always use a well-defined sort order
- if (pkg.splitCodePaths != null) {
- Arrays.sort(pkg.splitCodePaths);
+ for (File splitFile : splitFiles) {
+ parseSplitApk(pkg, splitFile, flags);
}
return pkg;
}
- public Package parseMonolithicPackage(File apkFile, DisplayMetrics metrics, int flags)
- throws PackageParserException {
- return parseMonolithicPackage(apkFile, metrics, flags, false);
- }
-
- public Package parseMonolithicPackage(File apkFile, DisplayMetrics metrics, int flags,
- boolean trustedOverlay) throws PackageParserException {
- final Package pkg = parseBaseApk(apkFile, metrics, flags, trustedOverlay);
+ /**
+ * Parse the given APK file, treating it as as a single monolithic package.
+ * <p>
+ * Note that this <em>does not</em> perform signature verification; that
+ * must be done separately in {@link #collectCertificates(Package, int)}.
+ */
+ public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
+ final Package pkg = parseBaseApk(apkFile, flags);
if (pkg != null) {
return pkg;
} else {
@@ -576,13 +639,15 @@ public class PackageParser {
}
}
- private Package parseBaseApk(File apkFile, DisplayMetrics metrics, int flags,
- boolean trustedOverlay) {
+ private Package parseBaseApk(File apkFile, int flags) {
+ final boolean trustedOverlay = (flags & PARSE_TRUSTED_OVERLAY) != 0;
+
mParseError = PackageManager.INSTALL_SUCCEEDED;
+ final String apkPath = apkFile.getAbsolutePath();
mArchiveSourcePath = apkFile.getAbsolutePath();
if (!apkFile.isFile()) {
- Slog.w(TAG, "Skipping dir: " + mArchiveSourcePath);
+ Slog.w(TAG, "Skipping dir: " + apkPath);
mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
return null;
}
@@ -591,14 +656,14 @@ public class PackageParser {
if ((flags&PARSE_IS_SYSTEM) == 0) {
// We expect to have non-.apk files in the system dir,
// so don't warn about them.
- Slog.w(TAG, "Skipping non-package file: " + mArchiveSourcePath);
+ Slog.w(TAG, "Skipping non-package file: " + apkPath);
}
mParseError = PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
return null;
}
if (DEBUG_JAR)
- Slog.d(TAG, "Scanning package: " + mArchiveSourcePath);
+ Slog.d(TAG, "Scanning package: " + apkPath);
XmlResourceParser parser = null;
AssetManager assmgr = null;
@@ -606,19 +671,18 @@ public class PackageParser {
boolean assetError = true;
try {
assmgr = new AssetManager();
- int cookie = assmgr.addAssetPath(mArchiveSourcePath);
+ int cookie = assmgr.addAssetPath(apkPath);
if (cookie != 0) {
- res = new Resources(assmgr, metrics, null);
+ res = new Resources(assmgr, mMetrics, null);
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
assetError = false;
} else {
- Slog.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
+ Slog.w(TAG, "Failed adding asset path:" + apkPath);
}
} catch (Exception e) {
- Slog.w(TAG, "Unable to read AndroidManifest.xml of "
- + mArchiveSourcePath, e);
+ Slog.w(TAG, "Unable to read AndroidManifest.xml of " + apkPath, e);
}
if (assetError) {
if (assmgr != null) assmgr.close();
@@ -641,9 +705,9 @@ public class PackageParser {
// just means to skip this app so don't make a fuss about it.
if (!mOnlyCoreApps || mParseError != PackageManager.INSTALL_SUCCEEDED) {
if (errorException != null) {
- Slog.w(TAG, mArchiveSourcePath, errorException);
+ Slog.w(TAG, apkPath, errorException);
} else {
- Slog.w(TAG, mArchiveSourcePath + " (at "
+ Slog.w(TAG, apkPath + " (at "
+ parser.getPositionDescription()
+ "): " + errorText[0]);
}
@@ -659,14 +723,16 @@ public class PackageParser {
parser.close();
assmgr.close();
- pkg.codePath = mArchiveSourcePath;
+ pkg.codePath = apkPath;
pkg.mSignatures = null;
return pkg;
}
- private void parseSplitApk(Package pkg, File apkFile, DisplayMetrics metrics, int flags,
- boolean trustedOverlay) throws PackageParserException {
+ private void parseSplitApk(Package pkg, File apkFile, int flags) throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+ mArchiveSourcePath = apkFile.getAbsolutePath();
+
// TODO: expand split APK parsing
pkg.splitCodePaths = ArrayUtils.appendElement(String.class, pkg.splitCodePaths,
apkFile.getAbsolutePath());
@@ -678,8 +744,9 @@ public class PackageParser {
* {@code AndroidManifest.xml}, {@code true} is returned.
*/
public void collectManifestDigest(Package pkg) throws PackageParserException {
+ // TODO: extend to gather digest for split APKs
try {
- final StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
+ final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
try {
final ZipEntry je = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
if (je != null) {
@@ -688,186 +755,127 @@ public class PackageParser {
} finally {
jarFile.close();
}
- } catch (IOException e) {
+ } catch (IOException | RuntimeException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Failed to collect manifest digest");
}
}
+ /**
+ * Collect certificates from all the APKs described in the given package,
+ * populating {@link Package#mSignatures}. This also asserts that all APK
+ * contents are signed correctly and consistently.
+ */
public void collectCertificates(Package pkg, int flags) throws PackageParserException {
- if (!collectCertificatesInternal(pkg, flags)) {
- throw new PackageParserException(mParseError, "Failed to collect certificates");
- }
- }
-
- private boolean collectCertificatesInternal(Package pkg, int flags) {
+ pkg.mCertificates = null;
pkg.mSignatures = null;
+ pkg.mSigningKeys = null;
- WeakReference<byte[]> readBufferRef;
- byte[] readBuffer = null;
- synchronized (mSync) {
- readBufferRef = mReadBuffer;
- if (readBufferRef != null) {
- mReadBuffer = null;
- readBuffer = readBufferRef.get();
- }
- if (readBuffer == null) {
- readBuffer = new byte[8192];
- readBufferRef = new WeakReference<byte[]>(readBuffer);
+ collectCertificates(pkg, new File(pkg.codePath), flags);
+
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (String splitCodePath : pkg.splitCodePaths) {
+ collectCertificates(pkg, new File(splitCodePath), flags);
}
}
+ }
+ private static void collectCertificates(Package pkg, File apkFile, int flags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ StrictJarFile jarFile = null;
try {
- StrictJarFile jarFile = new StrictJarFile(mArchiveSourcePath);
-
- Certificate[][] certs = null;
-
- if ((flags&PARSE_IS_SYSTEM) != 0) {
- // If this package comes from the system image, then we
- // can trust it... we'll just use the AndroidManifest.xml
- // to retrieve its signatures, not validating all of the
- // files.
- ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
- certs = loadCertificates(jarFile, jarEntry, readBuffer);
- if (certs == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " has no certificates at entry "
- + jarEntry.getName() + "; ignoring!");
- jarFile.close();
- mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
- return false;
- }
- if (DEBUG_JAR) {
- Slog.i(TAG, "File " + mArchiveSourcePath + ": entry=" + jarEntry
- + " certs=" + (certs != null ? certs.length : 0));
- if (certs != null) {
- final int N = certs.length;
- for (int i=0; i<N; i++) {
- Slog.i(TAG, " Public key: "
- + certs[i][0].getPublicKey().getEncoded()
- + " " + certs[i][0].getPublicKey());
- }
- }
- }
- } else {
- Iterator<ZipEntry> entries = jarFile.iterator();
- while (entries.hasNext()) {
- final ZipEntry je = entries.next();
- if (je.isDirectory()) continue;
+ jarFile = new StrictJarFile(apkPath);
- final String name = je.getName();
+ // Always verify manifest, regardless of source
+ final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
+ if (manifestEntry == null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Package " + apkPath + " has no manifest");
+ }
- if (name.startsWith("META-INF/"))
- continue;
+ final List<ZipEntry> toVerify = new ArrayList<>();
+ toVerify.add(manifestEntry);
- if (ANDROID_MANIFEST_FILENAME.equals(name)) {
- pkg.manifestDigest =
- ManifestDigest.fromInputStream(jarFile.getInputStream(je));
- }
+ // If we're parsing an untrusted package, verify all contents
+ if ((flags & PARSE_IS_SYSTEM) == 0) {
+ final Iterator<ZipEntry> i = jarFile.iterator();
+ while (i.hasNext()) {
+ final ZipEntry entry = i.next();
- final Certificate[][] localCerts = loadCertificates(jarFile, je, readBuffer);
- if (DEBUG_JAR) {
- Slog.i(TAG, "File " + mArchiveSourcePath + " entry " + je.getName()
- + ": certs=" + certs + " ("
- + (certs != null ? certs.length : 0) + ")");
- }
+ if (entry.isDirectory()) continue;
+ if (entry.getName().startsWith("META-INF/")) continue;
+ if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;
- if (localCerts == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " has no certificates at entry "
- + je.getName() + "; ignoring!");
- jarFile.close();
- mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
- return false;
- } else if (certs == null) {
- certs = localCerts;
- } else {
- // Ensure all certificates match.
- for (int i=0; i<certs.length; i++) {
- boolean found = false;
- for (int j=0; j<localCerts.length; j++) {
- if (certs[i] != null &&
- certs[i].equals(localCerts[j])) {
- found = true;
- break;
- }
- }
- if (!found || certs.length != localCerts.length) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " has mismatched certificates at entry "
- + je.getName() + "; ignoring!");
- jarFile.close();
- mParseError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
- return false;
- }
- }
- }
+ toVerify.add(entry);
}
}
- jarFile.close();
- synchronized (mSync) {
- mReadBuffer = readBufferRef;
- }
-
- if (!ArrayUtils.isEmpty(certs)) {
- pkg.mSignatures = convertToSignatures(certs);
- } else {
- Slog.e(TAG, "Package " + pkg.packageName
- + " has no certificates; ignoring!");
- mParseError = PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
- return false;
- }
+ // Verify that entries are signed consistently with the first entry
+ // we encountered. Note that for splits, certificates may have
+ // already been populated during an earlier parse of a base APK.
+ for (ZipEntry entry : toVerify) {
+ final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
+ if (ArrayUtils.isEmpty(entryCerts)) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Package " + apkPath + " has no certificates at entry "
+ + entry.getName());
+ }
- // Add the signing KeySet to the system
- pkg.mSigningKeys = new HashSet<PublicKey>();
- for (int i=0; i < certs.length; i++) {
- pkg.mSigningKeys.add(certs[i][0].getPublicKey());
+ if (pkg.mCertificates == null) {
+ pkg.mCertificates = entryCerts;
+ pkg.mSignatures = convertToSignatures(entryCerts);
+ pkg.mSigningKeys = new ArraySet<>();
+ for (int i = 0; i < entryCerts.length; i++) {
+ pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
+ }
+ } else {
+ final boolean certsMatch = (pkg.mCertificates.length == entryCerts.length)
+ && ArrayUtils.containsAll(pkg.mCertificates, entryCerts)
+ && ArrayUtils.containsAll(entryCerts, pkg.mCertificates);
+ if (!certsMatch) {
+ throw new PackageParserException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
+ + " has mismatched certificates at entry "
+ + entry.getName());
+ }
+ }
}
-
- } catch (CertificateEncodingException e) {
- Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
- mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
- return false;
- } catch (IOException e) {
- Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
- mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
- return false;
- } catch (SecurityException e) {
- Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
- mParseError = PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
- return false;
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception reading " + mArchiveSourcePath, e);
- mParseError = PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
- return false;
+ } catch (GeneralSecurityException | IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+ "Failed to collect certificates from " + apkPath, e);
+ } finally {
+ closeQuietly(jarFile);
}
-
- return true;
}
/**
* Only collect certificates on the manifest; does not validate signatures
* across remainder of package.
*/
- private static Signature[] collectCertificates(String packageFilePath) {
+ private static Signature[] collectManifestCertificates(File apkFile)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
try {
- final StrictJarFile jarFile = new StrictJarFile(packageFilePath);
+ final StrictJarFile jarFile = new StrictJarFile(apkPath);
try {
final ZipEntry jarEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
- if (jarEntry != null) {
- final Certificate[][] certs = loadCertificates(jarFile, jarEntry, null);
- return convertToSignatures(certs);
+ if (jarEntry == null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Package " + apkPath + " has no manifest");
}
+
+ final Certificate[][] certs = loadCertificates(jarFile, jarEntry);
+ return convertToSignatures(certs);
+
} finally {
jarFile.close();
}
- } catch (GeneralSecurityException e) {
- Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to collect certs from " + packageFilePath + ": " + e);
+ } catch (GeneralSecurityException | IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
+ "Failed to collect certificates from " + apkPath, e);
}
- return null;
}
private static Signature[] convertToSignatures(Certificate[][] certs)
@@ -879,67 +887,55 @@ public class PackageParser {
return res;
}
- /*
- * Utility method that retrieves just the package name and install
- * location from the apk location at the given file path.
- * @param packageFilePath file location of the apk
- * @param flags Special parse flags
- * @return PackageLite object with package information or null on failure.
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param apkFile path to a single APK
+ * @param flags optional parse flags, such as {@link #PARSE_GET_SIGNATURES}
*/
- public static ApkLite parseApkLite(String packageFilePath, int flags) {
+ public static ApkLite parseApkLite(File apkFile, int flags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
AssetManager assmgr = null;
- final XmlResourceParser parser;
- final Resources res;
+ XmlResourceParser parser = null;
try {
assmgr = new AssetManager();
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
- int cookie = assmgr.addAssetPath(packageFilePath);
+ int cookie = assmgr.addAssetPath(apkPath);
if (cookie == 0) {
- return null;
+ throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+ "Failed to parse " + apkPath);
}
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
- res = new Resources(assmgr, metrics, null);
+
+ final Resources res = new Resources(assmgr, metrics, null);
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
- } catch (Exception e) {
- if (assmgr != null) assmgr.close();
- Slog.w(TAG, "Unable to read AndroidManifest.xml of "
- + packageFilePath, e);
- return null;
- }
- // Only collect certificates on the manifest; does not validate
- // signatures across remainder of package.
- final Signature[] signatures;
- if ((flags & PARSE_GET_SIGNATURES) != 0) {
- signatures = collectCertificates(packageFilePath);
- } else {
- signatures = null;
- }
+ // Only collect certificates on the manifest; does not validate
+ // signatures across remainder of package.
+ final Signature[] signatures;
+ if ((flags & PARSE_GET_SIGNATURES) != 0) {
+ signatures = collectManifestCertificates(apkFile);
+ } else {
+ signatures = null;
+ }
- final AttributeSet attrs = parser;
- final String errors[] = new String[1];
- ApkLite packageLite = null;
- try {
- packageLite = parseApkLite(res, parser, attrs, flags, signatures, errors);
- } catch (PackageParserException e) {
- Slog.w(TAG, packageFilePath, e);
- } catch (IOException e) {
- Slog.w(TAG, packageFilePath, e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, packageFilePath, e);
+ final AttributeSet attrs = parser;
+ return parseApkLite(res, parser, attrs, flags, signatures);
+
+ } catch (XmlPullParserException | IOException | RuntimeException e) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to parse " + apkPath, e);
} finally {
if (parser != null) parser.close();
if (assmgr != null) assmgr.close();
}
- if (packageLite == null) {
- Slog.e(TAG, "parsePackageLite error: " + errors[0]);
- return null;
- }
- return packageLite;
}
private static String validateName(String name, boolean requiresSeparator) {
@@ -995,12 +991,16 @@ public class PackageParser {
}
}
- final String splitName = attrs.getAttributeValue(null, "split");
+ String splitName = attrs.getAttributeValue(null, "split");
if (splitName != null) {
- final String error = validateName(splitName, true);
- if (error != null) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
- "Invalid manifest split: " + error);
+ if (splitName.length() == 0) {
+ splitName = null;
+ } else {
+ final String error = validateName(splitName, true);
+ if (error != null) {
+ throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
+ "Invalid manifest split: " + error);
+ }
}
}
@@ -1009,8 +1009,8 @@ public class PackageParser {
}
private static ApkLite parseApkLite(Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, Signature[] signatures, String[] outError)
- throws IOException, XmlPullParserException, PackageParserException {
+ AttributeSet attrs, int flags, Signature[] signatures) throws IOException,
+ XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -1043,7 +1043,7 @@ public class PackageParser {
}
if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
- final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError);
+ final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags);
if (verifier != null) {
verifiers.add(verifier);
}
@@ -1793,7 +1793,7 @@ public class PackageParser {
}
}
- owner.mKeySetMapping = new HashMap<String, Set<PublicKey>>();
+ owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>();
for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) {
PublicKey key = e.getKey();
Set<String> keySetNames = e.getValue();
@@ -1801,7 +1801,7 @@ public class PackageParser {
if (owner.mKeySetMapping.containsKey(alias)) {
owner.mKeySetMapping.get(alias).add(key);
} else {
- Set<PublicKey> keys = new HashSet<PublicKey>();
+ ArraySet<PublicKey> keys = new ArraySet<PublicKey>();
keys.add(key);
owner.mKeySetMapping.put(alias, keys);
}
@@ -3427,8 +3427,7 @@ public class PackageParser {
}
private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException,
- IOException {
+ AttributeSet attrs, int flags) {
final TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestPackageVerifier);
@@ -3671,7 +3670,10 @@ public class PackageParser {
public String packageName;
// TODO: work towards making these paths invariant
+
+ /** Base APK */
public String codePath;
+ /** Split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
// For now we only support one application per package.
@@ -3717,7 +3719,8 @@ public class PackageParser {
public int mSharedUserLabel;
// Signatures that were read from the package.
- public Signature mSignatures[];
+ public Signature[] mSignatures;
+ public Certificate[][] mCertificates;
// For use by package manager service for quick lookup of
// preferred up order.
@@ -3779,8 +3782,8 @@ public class PackageParser {
/**
* Data used to feed the KeySetManager
*/
- public Set<PublicKey> mSigningKeys;
- public Map<String, Set<PublicKey>> mKeySetMapping;
+ public ArraySet<PublicKey> mSigningKeys;
+ public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;
public Package(String packageName) {
this.packageName = packageName;
@@ -3788,6 +3791,15 @@ public class PackageParser {
applicationInfo.uid = -1;
}
+ public Collection<String> getAllCodePaths() {
+ ArrayList<String> paths = new ArrayList<>();
+ paths.add(codePath);
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ Collections.addAll(paths, splitCodePaths);
+ }
+ return paths;
+ }
+
public void setPackageName(String newName) {
packageName = newName;
applicationInfo.packageName = newName;
@@ -4390,6 +4402,33 @@ public class PackageParser {
sCompatibilityModeEnabled = compatibilityModeEnabled;
}
+ private static AtomicReference<byte[]> sBuffer = new AtomicReference<byte[]>();
+
+ public static long readFullyIgnoringContents(InputStream in) throws IOException {
+ byte[] buffer = sBuffer.getAndSet(null);
+ if (buffer == null) {
+ buffer = new byte[4096];
+ }
+
+ int n = 0;
+ int count = 0;
+ while ((n = in.read(buffer, 0, buffer.length)) != -1) {
+ count += n;
+ }
+
+ sBuffer.set(buffer);
+ return count;
+ }
+
+ public static void closeQuietly(StrictJarFile jarFile) {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
public static class PackageParserException extends Exception {
public final int error;
@@ -4397,5 +4436,10 @@ public class PackageParser {
super(detailMessage);
this.error = error;
}
+
+ public PackageParserException(int error, String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ this.error = error;
+ }
}
}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 7251e7c3fa77..7f41ac1c5328 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -305,11 +305,9 @@ public class PackageManagerTests extends AndroidTestCase {
private PackageParser.Package parsePackage(Uri packageURI) throws PackageParserException {
final String archiveFilePath = packageURI.getPath();
- PackageParser packageParser = new PackageParser(archiveFilePath);
+ PackageParser packageParser = new PackageParser();
File sourceFile = new File(archiveFilePath);
- DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
- PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, metrics, 0);
+ PackageParser.Package pkg = packageParser.parseMonolithicPackage(sourceFile, 0);
packageParser = null;
return pkg;
}
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 2ed3d731b97f..52db30a82fba 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -27,6 +27,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
import android.content.res.ObbInfo;
import android.content.res.ObbScanner;
import android.net.Uri;
@@ -157,6 +158,7 @@ public class DefaultContainerService extends IntentService {
* @return Returns PackageInfoLite object containing
* the package info and recommended app location.
*/
+ @Override
public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
long threshold, String abiOverride) {
PackageInfoLite ret = new PackageInfoLite();
@@ -167,14 +169,13 @@ public class DefaultContainerService extends IntentService {
return ret;
}
- DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
-
- PackageParser.ApkLite pkg = PackageParser.parseApkLite(packagePath, 0);
- if (pkg == null) {
+ final File apkFile = new File(packagePath);
+ final PackageParser.ApkLite pkg;
+ try {
+ pkg = PackageParser.parseApkLite(apkFile, 0);
+ } catch (PackageParserException e) {
Slog.w(TAG, "Failed to parse package");
- final File apkFile = new File(packagePath);
if (!apkFile.exists()) {
ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
} else {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index dd3377142364..89ab2aeda45e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -29,6 +29,7 @@ import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
+import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
@@ -50,14 +51,10 @@ import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.io.Streams;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
@@ -297,11 +294,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Verify that all staged packages are internally consistent
for (File file : files) {
- final ApkLite info = PackageParser.parseApkLite(file.getAbsolutePath(),
- PackageParser.PARSE_GET_SIGNATURES);
- if (info == null) {
+ final ApkLite info;
+ try {
+ info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
+ } catch (PackageParserException e) {
throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
- "Failed to parse " + file);
+ "Failed to parse " + file + ": " + e);
}
if (!seenSplits.add(info.splitName)) {
@@ -356,11 +354,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Missing existing base package for " + mPackageName);
}
- final ApkLite info = PackageParser.parseApkLite(app.sourceDir,
- PackageParser.PARSE_GET_SIGNATURES);
- if (info == null) {
+ final ApkLite info;
+ try {
+ info = PackageParser.parseApkLite(new File(app.sourceDir),
+ PackageParser.PARSE_GET_SIGNATURES);
+ } catch (PackageParserException e) {
throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
- "Failed to parse existing base " + app.sourceDir);
+ "Failed to parse existing base " + app.sourceDir + ": " + e);
}
assertPackageConsistent("Existing base", info.packageName, info.versionCode,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dc7645576896..2f40f2ab19e2 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4199,14 +4199,18 @@ public class PackageManagerService extends IPackageManager.Stub {
String scanPath = scanFile.getPath();
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
parseFlags |= mDefParseFlags;
- PackageParser pp = new PackageParser(scanPath);
+ PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
+ pp.setDisplayMetrics(mMetrics);
+
+ if ((scanMode & SCAN_TRUSTED_OVERLAY) != 0) {
+ parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
+ }
final PackageParser.Package pkg;
try {
- pkg = pp.parseMonolithicPackage(scanFile, mMetrics, parseFlags,
- (scanMode & SCAN_TRUSTED_OVERLAY) != 0);
+ pkg = pp.parseMonolithicPackage(scanFile, parseFlags);
} catch (PackageParserException e) {
mLastScanError = e.error;
return null;
@@ -4637,12 +4641,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
- final ArrayList<String> paths = new ArrayList<>();
- paths.add(pkg.codePath);
- if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
- Collections.addAll(paths, pkg.splitCodePaths);
- }
-
+ final Collection<String> paths = pkg.getAllCodePaths();
for (String path : paths) {
try {
boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path,
@@ -4832,10 +4831,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
if (p != null) {
- usesLibraryFiles.add(p.codePath);
- if (!ArrayUtils.isEmpty(p.splitCodePaths)) {
- Collections.addAll(usesLibraryFiles, p.splitCodePaths);
- }
+ usesLibraryFiles.addAll(p.getAllCodePaths());
}
}
@@ -5674,7 +5670,8 @@ public class PackageManagerService extends IPackageManager.Stub {
try {
ksm.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
if (pkg.mKeySetMapping != null) {
- for (Map.Entry<String, Set<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) {
+ for (Map.Entry<String, ArraySet<PublicKey>> entry :
+ pkg.mKeySetMapping.entrySet()) {
if (entry.getValue() != null) {
ksm.addDefinedKeySetToPackage(pkg.packageName,
entry.getValue(), entry.getKey());
@@ -9713,7 +9710,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- };
+ }
static String getAsecPackageName(String packageCid) {
int idx = packageCid.lastIndexOf("-");
@@ -10206,13 +10203,13 @@ public class PackageManagerService extends IPackageManager.Stub {
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
- PackageParser pp = new PackageParser(tmpPackageFile.getPath());
+ PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
+ pp.setDisplayMetrics(mMetrics);
final PackageParser.Package pkg;
try {
- pkg = pp.parseMonolithicPackage(tmpPackageFile, mMetrics,
- parseFlags);
+ pkg = pp.parseMonolithicPackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.returnCode = e.error;
return;