From 7029b75f67bf1f026c8be11c0bb0b3c18e12ad29 Mon Sep 17 00:00:00 2001 From: Winson Date: Fri, 15 May 2020 15:03:22 -0700 Subject: [PATCH] Remove PlatformCompat dependency from getPackageArchiveInfo It requires a permission which we can't force apps to take to maintain backwards compatibility. We also arguably cannot because it leaks visibility, although only for debuggable apps/non-release builds. Instead, there's a new static method for getting the raw targetSdk to gate against and the check is done manually, ignoring enabled/disabled state. This will cause a mismatch between certain apps and some system services like AppIntegrityManager, but the effects should be minimal if we assume that most people ship valid APKs. At worse the integrity check will pass an APK that PM will fail, which doesn't break the feature. Bug: 156356591 Bug: 156778241 Test: manual device boots Change-Id: I877a5061476b86b9d63c34e75f16b38be8c3e1c2 --- .../android/content/pm/PackageManager.java | 5 ++++- .../pm/parsing/ParsingPackageUtils.java | 15 +++++++++++--- .../content/pm/parsing/result/ParseInput.java | 20 +++++++++++++++++++ .../pm/parsing/result/ParseTypeImpl.java | 15 ++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1e9cddbb684ad..9832bc1b79d23 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -51,7 +51,9 @@ import android.content.pm.dex.ArtManager; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.ParsingPackage; import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Rect; @@ -6061,7 +6063,8 @@ public abstract class PackageManager { boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0 || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0; - ParseResult result = ParsingPackageUtils.parseDefaultOneTime( + ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset(); + ParseResult result = ParsingPackageUtils.parseDefault(input, new File(archiveFilePath), 0, collectCertificates); if (result.isError()) { return null; diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index cb2943122781c..5a7947503d5c7 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -131,14 +131,23 @@ public class ParsingPackageUtils { public static final String TAG = ParsingUtils.TAG; /** - * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off - * request, without caching the input object and without querying the internal system state - * for feature support. + * @see #parseDefault(ParseInput, File, int, boolean) */ @NonNull public static ParseResult parseDefaultOneTime(File file, @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) { ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); + return parseDefault(input, file, parseFlags, collectCertificates); + } + + /** + * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off + * request, without caching the input object and without querying the internal system state + * for feature support. + */ + @NonNull + public static ParseResult parseDefault(ParseInput input, File file, + @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) { ParseResult result; ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() { diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java index d5898b7f57a1a..0fb18aee1eb41 100644 --- a/core/java/android/content/pm/parsing/result/ParseInput.java +++ b/core/java/android/content/pm/parsing/result/ParseInput.java @@ -16,6 +16,7 @@ package android.content.pm.parsing.result; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.ChangeId; @@ -69,6 +70,25 @@ public interface ParseInput { @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long RESOURCES_ARSC_COMPRESSED = 132742131; + + /** + * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo + * cannot read the targetSdk info from the changeId because it requires the + * READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the + * server process. This should be removed once an alternative is found, or if the API + * is removed. + * @return the targetSdk that this change is gated on (> check), or -1 if disabled + */ + @IntRange(from = -1, to = Integer.MAX_VALUE) + public static int getTargetSdkForChange(long changeId) { + if (changeId == MISSING_APP_TAG + || changeId == EMPTY_INTENT_ACTION_CATEGORY + || changeId == RESOURCES_ARSC_COMPRESSED) { + return Build.VERSION_CODES.Q; + } + + return -1; + } } ParseResult success(ResultType result); diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java index 91e571be3d89c..14992fb2a4d16 100644 --- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java +++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java @@ -64,6 +64,21 @@ public class ParseTypeImpl implements ParseInput, ParseResult { private String mPackageName; private Integer mTargetSdkVersion; + /** + * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where + * {@link IPlatformCompat} cannot be used because the cross-package READ_COMPAT_CHANGE_CONFIG + * permission cannot be obtained. + */ + public static ParseTypeImpl forParsingWithoutPlatformCompat() { + return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> { + int gateSdkVersion = DeferredError.getTargetSdkForChange(changeId); + if (gateSdkVersion == -1) { + return false; + } + return targetSdkVersion > gateSdkVersion; + }); + } + /** * Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use * with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is