Loading core/java/android/content/pm/dex/DexMetadataHelper.java +100 −2 Original line number Diff line number Diff line Loading @@ -22,17 +22,26 @@ import static android.content.pm.PackageParser.APK_FILE_EXTENSION; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.jar.StrictJarFile; import android.util.JsonReader; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; /** * Helper class used to compute and validate the location of dex metadata files. Loading @@ -40,6 +49,12 @@ import java.util.Map; * @hide */ public class DexMetadataHelper { public static final String TAG = "DexMetadataHelper"; /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */ private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest"; private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private DexMetadataHelper() {} Loading Loading @@ -147,14 +162,31 @@ public class DexMetadataHelper { /** * Validate that the given file is a dex metadata archive. * This is just a validation that the file is a zip archive. * This is just a validation that the file is a zip archive that contains a manifest.json * with the package name and version code. * * @throws PackageParserException if the file is not a .dm file. */ public static void validateDexMetadataFile(String dmaPath) throws PackageParserException { public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode) throws PackageParserException { validateDexMetadataFile(dmaPath, packageName, versionCode, SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false)); } @VisibleForTesting public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode, boolean requireManifest) throws PackageParserException { StrictJarFile jarFile = null; if (DEBUG) { Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName + ", " + versionCode); } try { jarFile = new StrictJarFile(dmaPath, false, false); validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode, requireManifest); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening " + dmaPath, e); Loading @@ -168,6 +200,72 @@ public class DexMetadataHelper { } } /** Ensure that packageName and versionCode match the manifest.json in the .dm file */ private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile, String packageName, long versionCode, boolean requireManifest) throws IOException, PackageParserException { if (!requireManifest) { if (DEBUG) { Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + " manifest.json check skipped"); } return; } ZipEntry zipEntry = jarFile.findEntry("manifest.json"); if (zipEntry == null) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Missing manifest.json in " + dmaPath); } InputStream inputStream = jarFile.getInputStream(zipEntry); JsonReader reader; try { reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening manifest.json in " + dmaPath, e); } String jsonPackageName = null; long jsonVersionCode = -1; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("packageName")) { jsonPackageName = reader.nextString(); } else if (name.equals("versionCode")) { jsonVersionCode = reader.nextLong(); } else { reader.skipValue(); } } reader.endObject(); if (jsonPackageName == null || jsonVersionCode == -1) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " is missing 'packageName' and/or 'versionCode'"); } if (!jsonPackageName.equals(packageName)) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName + ", expected: " + packageName); } if (versionCode != jsonVersionCode) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode + ", expected: " + versionCode); } if (DEBUG) { Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName + ", " + versionCode + ": successful"); } } /** * Validates that all dex metadata paths in the given list have a matching apk. * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file). Loading services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +3 −1 Original line number Diff line number Diff line Loading @@ -125,8 +125,10 @@ public class AndroidPackageUtils { public static void validatePackageDexMetadata(AndroidPackage pkg) throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); String packageName = pkg.getPackageName(); long versionCode = pkg.toAppInfoWithoutState().longVersionCode; for (String dexMetadata : apkToDexMetadataList) { DexMetadataHelper.validateDexMetadataFile(dexMetadata); DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode); } } Loading services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +190 −12 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; Loading @@ -57,6 +58,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; Loading @@ -66,6 +69,9 @@ import java.util.zip.ZipOutputStream; public class DexMetadataHelperTest { private static final String APK_FILE_EXTENSION = ".apk"; private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private static final String DEX_METADATA_PACKAGE_NAME = "com.android.frameworks.servicestests.install_split"; private static long DEX_METADATA_VERSION_CODE = 30; @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); Loading @@ -78,12 +84,46 @@ public class DexMetadataHelperTest { } private File createDexMetadataFile(String apkFileName) throws IOException { return createDexMetadataFile(apkFileName, /*validManifest=*/true); } private File createDexMetadataFile(String apkFileName, boolean validManifest) throws IOException { return createDexMetadataFile(apkFileName,DEX_METADATA_PACKAGE_NAME, DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, validManifest); } private File createDexMetadataFile(String apkFileName, String packageName, Long versionCode, boolean emptyManifest, boolean validManifest) throws IOException { File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION, DEX_METADATA_FILE_EXTENSION)); try (FileOutputStream fos = new FileOutputStream(dmFile)) { try (ZipOutputStream zipOs = new ZipOutputStream(fos)) { zipOs.putNextEntry(new ZipEntry("primary.prof")); zipOs.closeEntry(); if (validManifest) { zipOs.putNextEntry(new ZipEntry("manifest.json")); if (!emptyManifest) { String manifestStr = "{"; if (packageName != null) { manifestStr += "\"packageName\": " + "\"" + packageName + "\""; if (versionCode != null) { manifestStr += ", "; } } if (versionCode != null) { manifestStr += " \"versionCode\": " + versionCode; } manifestStr += "}"; byte[] bytes = manifestStr.getBytes(StandardCharsets.UTF_8); zipOs.write(bytes, /*off=*/0, /*len=*/bytes.length); } zipOs.closeEntry(); } } } return dmFile; Loading @@ -98,17 +138,38 @@ public class DexMetadataHelperTest { return outFile; } private static void validatePackageDexMetadata(AndroidPackage pkg, boolean requireManifest) throws PackageParserException { Collection<String> apkToDexMetadataList = AndroidPackageUtils.getPackageDexMetadata(pkg).values(); String packageName = pkg.getPackageName(); long versionCode = pkg.toAppInfoWithoutState().longVersionCode; for (String dexMetadata : apkToDexMetadataList) { DexMetadataHelper.validateDexMetadataFile( dexMetadata, packageName, versionCode, requireManifest); } } private static void validatePackageDexMetatadataVaryingRequireManifest(ParsedPackage pkg) throws PackageParserException { validatePackageDexMetadata(pkg, /*requireManifest=*/true); validatePackageDexMetadata(pkg, /*requireManifest=*/false); } @Test public void testParsePackageWithDmFileValid() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(1, packageDexMetadata.size()); String baseDexMetadata = packageDexMetadata.get(pkg.getBaseCodePath()); assertNotNull(baseDexMetadata); assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.getBaseCodePath())); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -118,7 +179,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_base.apk"); createDexMetadataFile("install_split_feature_a.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(2, packageDexMetadata.size()); Loading @@ -129,6 +190,9 @@ public class DexMetadataHelperTest { String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]); assertNotNull(splitDexMetadata); assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0])); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -137,7 +201,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_feature_a.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(1, packageDexMetadata.size()); Loading @@ -145,6 +209,9 @@ public class DexMetadataHelperTest { String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]); assertNotNull(splitDexMetadata); assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0])); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -153,9 +220,17 @@ public class DexMetadataHelperTest { File invalidDmFile = new File(mTmpDir, "install_split_base.dm"); Files.createFile(invalidDmFile.toPath()); try { ParsedPackage pkg = new TestPackageParser2() .parsePackage(mTmpDir, 0 /* flags */, false); AndroidPackageUtils.validatePackageDexMetadata(pkg); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/false); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } Loading @@ -171,9 +246,112 @@ public class DexMetadataHelperTest { Files.createFile(invalidDmFile.toPath()); try { ParsedPackage pkg = new TestPackageParser2() .parsePackage(mTmpDir, 0 /* flags */, false); AndroidPackageUtils.validatePackageDexMetadata(pkg); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/false); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileInvalidManifest() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*validManifest=*/false); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing manifest.json in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileEmptyManifest() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter", /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty manifest.json in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileBadPackageName() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name", DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: bad package name in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileBadVersionCode() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: bad version code in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileMissingPackageName() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/null, DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing package name in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileMissingVersionCode() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing version code in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } Loading @@ -186,7 +364,7 @@ public class DexMetadataHelperTest { try { DexMetadataHelper.validateDexPaths(mTmpDir.list()); fail("Should fail validation"); fail("Should fail validation: split .dm filename unmatched against .apk"); } catch (IllegalStateException e) { // expected. } Loading @@ -202,7 +380,7 @@ public class DexMetadataHelperTest { try { DexMetadataHelper.validateDexPaths(mTmpDir.list()); fail("Should fail validation"); fail("Should fail validation: .dm filename has no match against .apk"); } catch (IllegalStateException e) { // expected. } Loading @@ -214,7 +392,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); File dm = createDexMetadataFile("install_split_base.apk"); ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */); ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, /*flags=*/0); if (result.isError()) { throw new IllegalStateException(result.getErrorMessage(), result.getException()); } Loading Loading
core/java/android/content/pm/dex/DexMetadataHelper.java +100 −2 Original line number Diff line number Diff line Loading @@ -22,17 +22,26 @@ import static android.content.pm.PackageParser.APK_FILE_EXTENSION; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.jar.StrictJarFile; import android.util.JsonReader; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; /** * Helper class used to compute and validate the location of dex metadata files. Loading @@ -40,6 +49,12 @@ import java.util.Map; * @hide */ public class DexMetadataHelper { public static final String TAG = "DexMetadataHelper"; /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */ private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest"; private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private DexMetadataHelper() {} Loading Loading @@ -147,14 +162,31 @@ public class DexMetadataHelper { /** * Validate that the given file is a dex metadata archive. * This is just a validation that the file is a zip archive. * This is just a validation that the file is a zip archive that contains a manifest.json * with the package name and version code. * * @throws PackageParserException if the file is not a .dm file. */ public static void validateDexMetadataFile(String dmaPath) throws PackageParserException { public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode) throws PackageParserException { validateDexMetadataFile(dmaPath, packageName, versionCode, SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false)); } @VisibleForTesting public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode, boolean requireManifest) throws PackageParserException { StrictJarFile jarFile = null; if (DEBUG) { Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName + ", " + versionCode); } try { jarFile = new StrictJarFile(dmaPath, false, false); validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode, requireManifest); } catch (IOException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening " + dmaPath, e); Loading @@ -168,6 +200,72 @@ public class DexMetadataHelper { } } /** Ensure that packageName and versionCode match the manifest.json in the .dm file */ private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile, String packageName, long versionCode, boolean requireManifest) throws IOException, PackageParserException { if (!requireManifest) { if (DEBUG) { Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + " manifest.json check skipped"); } return; } ZipEntry zipEntry = jarFile.findEntry("manifest.json"); if (zipEntry == null) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Missing manifest.json in " + dmaPath); } InputStream inputStream = jarFile.getInputStream(zipEntry); JsonReader reader; try { reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "Error opening manifest.json in " + dmaPath, e); } String jsonPackageName = null; long jsonVersionCode = -1; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("packageName")) { jsonPackageName = reader.nextString(); } else if (name.equals("versionCode")) { jsonVersionCode = reader.nextLong(); } else { reader.skipValue(); } } reader.endObject(); if (jsonPackageName == null || jsonVersionCode == -1) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " is missing 'packageName' and/or 'versionCode'"); } if (!jsonPackageName.equals(packageName)) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName + ", expected: " + packageName); } if (versionCode != jsonVersionCode) { throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA, "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode + ", expected: " + versionCode); } if (DEBUG) { Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName + ", " + versionCode + ": successful"); } } /** * Validates that all dex metadata paths in the given list have a matching apk. * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file). Loading
services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +3 −1 Original line number Diff line number Diff line Loading @@ -125,8 +125,10 @@ public class AndroidPackageUtils { public static void validatePackageDexMetadata(AndroidPackage pkg) throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); String packageName = pkg.getPackageName(); long versionCode = pkg.toAppInfoWithoutState().longVersionCode; for (String dexMetadata : apkToDexMetadataList) { DexMetadataHelper.validateDexMetadataFile(dexMetadata); DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode); } } Loading
services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +190 −12 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; Loading @@ -57,6 +58,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; Loading @@ -66,6 +69,9 @@ import java.util.zip.ZipOutputStream; public class DexMetadataHelperTest { private static final String APK_FILE_EXTENSION = ".apk"; private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; private static final String DEX_METADATA_PACKAGE_NAME = "com.android.frameworks.servicestests.install_split"; private static long DEX_METADATA_VERSION_CODE = 30; @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); Loading @@ -78,12 +84,46 @@ public class DexMetadataHelperTest { } private File createDexMetadataFile(String apkFileName) throws IOException { return createDexMetadataFile(apkFileName, /*validManifest=*/true); } private File createDexMetadataFile(String apkFileName, boolean validManifest) throws IOException { return createDexMetadataFile(apkFileName,DEX_METADATA_PACKAGE_NAME, DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, validManifest); } private File createDexMetadataFile(String apkFileName, String packageName, Long versionCode, boolean emptyManifest, boolean validManifest) throws IOException { File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION, DEX_METADATA_FILE_EXTENSION)); try (FileOutputStream fos = new FileOutputStream(dmFile)) { try (ZipOutputStream zipOs = new ZipOutputStream(fos)) { zipOs.putNextEntry(new ZipEntry("primary.prof")); zipOs.closeEntry(); if (validManifest) { zipOs.putNextEntry(new ZipEntry("manifest.json")); if (!emptyManifest) { String manifestStr = "{"; if (packageName != null) { manifestStr += "\"packageName\": " + "\"" + packageName + "\""; if (versionCode != null) { manifestStr += ", "; } } if (versionCode != null) { manifestStr += " \"versionCode\": " + versionCode; } manifestStr += "}"; byte[] bytes = manifestStr.getBytes(StandardCharsets.UTF_8); zipOs.write(bytes, /*off=*/0, /*len=*/bytes.length); } zipOs.closeEntry(); } } } return dmFile; Loading @@ -98,17 +138,38 @@ public class DexMetadataHelperTest { return outFile; } private static void validatePackageDexMetadata(AndroidPackage pkg, boolean requireManifest) throws PackageParserException { Collection<String> apkToDexMetadataList = AndroidPackageUtils.getPackageDexMetadata(pkg).values(); String packageName = pkg.getPackageName(); long versionCode = pkg.toAppInfoWithoutState().longVersionCode; for (String dexMetadata : apkToDexMetadataList) { DexMetadataHelper.validateDexMetadataFile( dexMetadata, packageName, versionCode, requireManifest); } } private static void validatePackageDexMetatadataVaryingRequireManifest(ParsedPackage pkg) throws PackageParserException { validatePackageDexMetadata(pkg, /*requireManifest=*/true); validatePackageDexMetadata(pkg, /*requireManifest=*/false); } @Test public void testParsePackageWithDmFileValid() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(1, packageDexMetadata.size()); String baseDexMetadata = packageDexMetadata.get(pkg.getBaseCodePath()); assertNotNull(baseDexMetadata); assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.getBaseCodePath())); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -118,7 +179,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_base.apk"); createDexMetadataFile("install_split_feature_a.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(2, packageDexMetadata.size()); Loading @@ -129,6 +190,9 @@ public class DexMetadataHelperTest { String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]); assertNotNull(splitDexMetadata); assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0])); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -137,7 +201,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a); createDexMetadataFile("install_split_feature_a.apk"); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg); assertEquals(1, packageDexMetadata.size()); Loading @@ -145,6 +209,9 @@ public class DexMetadataHelperTest { String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]); assertNotNull(splitDexMetadata); assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0])); // Should throw no exceptions. validatePackageDexMetatadataVaryingRequireManifest(pkg); } @Test Loading @@ -153,9 +220,17 @@ public class DexMetadataHelperTest { File invalidDmFile = new File(mTmpDir, "install_split_base.dm"); Files.createFile(invalidDmFile.toPath()); try { ParsedPackage pkg = new TestPackageParser2() .parsePackage(mTmpDir, 0 /* flags */, false); AndroidPackageUtils.validatePackageDexMetadata(pkg); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/false); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } Loading @@ -171,9 +246,112 @@ public class DexMetadataHelperTest { Files.createFile(invalidDmFile.toPath()); try { ParsedPackage pkg = new TestPackageParser2() .parsePackage(mTmpDir, 0 /* flags */, false); AndroidPackageUtils.validatePackageDexMetadata(pkg); ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/false); fail("Should fail validation: empty .dm file"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileInvalidManifest() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*validManifest=*/false); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing manifest.json in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileEmptyManifest() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter", /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: empty manifest.json in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileBadPackageName() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name", DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: bad package name in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileBadVersionCode() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: bad version code in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileMissingPackageName() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", /*packageName=*/null, DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing package name in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } } @Test public void testParsePackageWithDmFileMissingVersionCode() throws IOException, PackageParserException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME, /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true); try { ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false); validatePackageDexMetadata(pkg, /*requireManifest=*/true); fail("Should fail validation: missing version code in the .dm archive"); } catch (PackageParserException e) { assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA); } Loading @@ -186,7 +364,7 @@ public class DexMetadataHelperTest { try { DexMetadataHelper.validateDexPaths(mTmpDir.list()); fail("Should fail validation"); fail("Should fail validation: split .dm filename unmatched against .apk"); } catch (IllegalStateException e) { // expected. } Loading @@ -202,7 +380,7 @@ public class DexMetadataHelperTest { try { DexMetadataHelper.validateDexPaths(mTmpDir.list()); fail("Should fail validation"); fail("Should fail validation: .dm filename has no match against .apk"); } catch (IllegalStateException e) { // expected. } Loading @@ -214,7 +392,7 @@ public class DexMetadataHelperTest { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); File dm = createDexMetadataFile("install_split_base.apk"); ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */); ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, /*flags=*/0); if (result.isError()) { throw new IllegalStateException(result.getErrorMessage(), result.getException()); } Loading