diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 66c500063861d..6534f5b324563 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -159,6 +159,7 @@ public class PackageParser { private static final String TAG_SUPPORTS_INPUT = "supports-input"; private static final String TAG_EAT_COMMENT = "eat-comment"; private static final String TAG_PACKAGE = "package"; + private static final String TAG_RESTRICT_UPDATE = "restrict-update"; // These are the tags supported by child packages private static final Set CHILD_PACKAGE_TAGS = new ArraySet<>(); @@ -1639,9 +1640,9 @@ public class PackageParser { /** * This is the common parsing routing for handling parent and child * packages in a base APK. The difference between parent and child - * parsing is that some targs are not supported by child packages as + * parsing is that some tags are not supported by child packages as * well as some manifest attributes are ignored. The implementation - * assumes the calling code already handled the manifest tag if needed + * assumes the calling code has already handled the manifest tag if needed * (this applies to the parent only). * * @param pkg The package which to populate @@ -2089,6 +2090,29 @@ public class PackageParser { // If parsing a child failed the error is already set return null; } + + } else if (tagName.equals(TAG_RESTRICT_UPDATE)) { + if ((flags & PARSE_IS_SYSTEM_DIR) != 0) { + sa = res.obtainAttributes(parser, + com.android.internal.R.styleable.AndroidManifestRestrictUpdate); + final String hash = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestRestrictUpdate_hash, 0); + sa.recycle(); + + pkg.restrictUpdateHash = null; + if (hash != null) { + final int hashLength = hash.length(); + final byte[] hashBytes = new byte[hashLength / 2]; + for (int i = 0; i < hashLength; i += 2){ + hashBytes[i/2] = (byte) ((Character.digit(hash.charAt(i), 16) << 4) + + Character.digit(hash.charAt(i + 1), 16)); + } + pkg.restrictUpdateHash = hashBytes; + } + } + + XmlUtils.skipCurrentTag(parser); + } else if (RIGID_PARSER) { outError[0] = "Bad element under : " + parser.getName(); @@ -4822,6 +4846,8 @@ public class PackageParser { */ public boolean use32bitAbi; + public byte[] restrictUpdateHash; + public Package(String packageName) { this.packageName = packageName; applicationInfo.packageName = packageName; diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 3fd75f70c0895..191afe5fd5211 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2291,4 +2291,14 @@ + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 11df8e5fa5890..c91e09fd39d03 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2715,6 +2715,7 @@ + diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4b0eeedbe1255..397e1ceabfcb0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -266,6 +266,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; @@ -274,6 +275,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -13787,6 +13789,13 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } + private static void updateDigest(MessageDigest digest, File file) throws IOException { + try (DigestInputStream digestStream = + new DigestInputStream(new FileInputStream(file), digest)) { + while (digestStream.read() != -1) {} // nothing to do; just plow through the file + } + } + private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user, String installerPackageName, PackageInstalledInfo res) { final boolean isEphemeral = (policyFlags & PackageParser.PARSE_IS_EPHEMERAL) != 0; @@ -13841,6 +13850,32 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // don't allow a system upgrade unless the upgrade hash matches + if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) { + byte[] digestBytes = null; + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-512"); + updateDigest(digest, new File(pkg.baseCodePath)); + if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { + for (String path : pkg.splitCodePaths) { + updateDigest(digest, new File(path)); + } + } + digestBytes = digest.digest(); + } catch (NoSuchAlgorithmException | IOException e) { + res.setError(INSTALL_FAILED_INVALID_APK, + "Could not compute hash: " + pkgName); + return; + } + if (!Arrays.equals(oldPackage.restrictUpdateHash, digestBytes)) { + res.setError(INSTALL_FAILED_INVALID_APK, + "New package fails restrict-update check: " + pkgName); + return; + } + // retain upgrade restriction + pkg.restrictUpdateHash = oldPackage.restrictUpdateHash; + } + // Check for shared user id changes String invalidPackageName = getParentOrChildPackageChangedSharedUser(oldPackage, pkg);