Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 727da64b authored by Winson's avatar Winson Committed by Winson Chiu
Browse files

Gate stricter manifest enforcement on targetSdk R

Two package parsing issues have been promoted to failures in R:
a missing <application>/<instrumentation> tag, and an empty
"android:name" attribute. The latter due to a bug in the parsing
code.

These need to be gated by targetSdkVersion so that APKs built for
previous versions can still scan/install properly. This change
introduces support for this through a framework that leverages
@ChangeId to introduce individually toggle-able errors, in
case a developer needs to install an app that isn't completely
migrated for a new SDK version yet.

The ignoreError method was removed from ParseResult as the errors
it was used for were manually compared to PackageParser and it's
likely they can be hard errors without breaking anything.

This also adds tests for general ParseInput/Result behavior.

Exempt-From-Owner-Approval: AppIntegrity already approved in older PS.

Bug: 150776642

Test: atest android.content.pm.parsing.result.ParseInputAndResultTest
Test: atest com.android.server.pm.parsing.PackageParsingDeferErrorTest
Test: atest com.android.server.integrity.AppIntegrityManagerServiceImplTest
Test: atest com.android.server.pm.parsing

Change-Id: Id53a2e65f6e5e4dee9a41cc77007275b3a220ac3
parent 63ff5bb6
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper
import android.content.pm.parsing.ParsingPackageImpl
import android.content.pm.parsing.ParsingPackageRead
import android.content.pm.parsing.ParsingPackageUtils
import android.content.pm.parsing.result.ParseInput
import android.content.pm.parsing.result.ParseTypeImpl
import android.content.res.TypedArray
import android.perftests.utils.BenchmarkState
@@ -173,7 +174,10 @@ class PackageParsingPerfTest {

    class ParallelParser2(cacher: PackageCacher2? = null)
        : ParallelParser<ParsingPackageRead>(cacher) {
        val input = ThreadLocal.withInitial { ParseTypeImpl() }
        val input = ThreadLocal.withInitial {
            // For testing, just disable enforcement to avoid hooking up to compat framework
            ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
        }
        val parser = ParsingPackageUtils(false, null, null,
            object : ParsingPackageUtils.Callback {
                override fun hasFeature(feature: String) = true
+90 −33
Original line number Diff line number Diff line
@@ -65,7 +65,9 @@ import android.content.pm.parsing.component.ParsedProviderUtils;
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.parsing.component.ParsedServiceUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
@@ -126,6 +128,51 @@ 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.
     */
    @NonNull
    public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, int flags,
            @NonNull ParseInput.Callback inputCallback, @NonNull Callback callback) {
        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
            // Caller expressed no opinion about what encryption
            // aware/unaware components they want to see, so match both
            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
        }

        ParseInput input = new ParseTypeImpl(inputCallback).reset();
        ParseResult<ParsingPackage> result;


        ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, callback);
        try {
            result = parser.parsePackage(input, file, flags);
            if (result.isError()) {
                return result;
            }
        } catch (PackageParser.PackageParserException e) {
            return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Error parsing package", e);
        }

        try {
            ParsingPackage pkg = result.getResult();
            if ((flags & PackageManager.GET_SIGNATURES) != 0
                    || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
                ParsingPackageUtils.collectCertificates(pkg, false /* skipVerify */);
            }

            return input.success(pkg);
        } catch (PackageParser.PackageParserException e) {
            return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Error collecting package certificates", e);
        }
    }

    private boolean mOnlyCoreApps;
    private String[] mSeparateProcesses;
    private DisplayMetrics mDisplayMetrics;
@@ -456,10 +503,11 @@ public class ParsingPackageUtils {
        }

        if (!foundApp) {
            return input.error(
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY,
                    "<manifest> does not contain an <application>"
            );
            ParseResult<?> deferResult = input.deferError(
                    "<manifest> does not contain an <application>", DeferredError.MISSING_APP_TAG);
            if (deferResult.isError()) {
                return input.error(deferResult);
            }
        }

        return input.success(pkg);
@@ -663,10 +711,12 @@ public class ParsingPackageUtils {
        }

        if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
            return input.error(
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY,
                    "<manifest> does not contain an <application> or <instrumentation>"
            );
            ParseResult<?> deferResult = input.deferError(
                    "<manifest> does not contain an <application> or <instrumentation>",
                    DeferredError.MISSING_APP_TAG);
            if (deferResult.isError()) {
                return input.error(deferResult);
            }
        }

        if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
@@ -758,11 +808,9 @@ public class ParsingPackageUtils {
            return input.success(pkg);
        }

        ParseResult nameResult = validateName(input, str, true, true);
        if (!"android".equals(pkg.getPackageName())) {
            ParseResult<?> nameResult = validateName(input, str, true, true);
            if (nameResult.isError()) {
            if ("android".equals(pkg.getPackageName())) {
                nameResult.ignoreError();
            } else {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
                        "<manifest> specifies bad sharedUserId name \"" + str + "\": "
                                + nameResult.getErrorMessage());
@@ -1166,6 +1214,20 @@ public class ParsingPackageUtils {
                    targetCode = minCode;
                }

                ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
                        targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
                if (targetSdkVersionResult.isError()) {
                    return input.error(targetSdkVersionResult);
                }

                int targetSdkVersion = targetSdkVersionResult.getResult();

                ParseResult<?> deferResult =
                        input.enableDeferredError(pkg.getPackageName(), targetSdkVersion);
                if (deferResult.isError()) {
                    return input.error(deferResult);
                }

                ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
                        PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input);
                if (minSdkVersionResult.isError()) {
@@ -1174,14 +1236,6 @@ public class ParsingPackageUtils {

                int minSdkVersion = minSdkVersionResult.getResult();

                ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
                        targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
                if (targetSdkVersionResult.isError()) {
                    return input.error(targetSdkVersionResult);
                }

                int targetSdkVersion = minSdkVersionResult.getResult();

                pkg.setMinSdkVersion(minSdkVersion)
                        .setTargetSdkVersion(targetSdkVersion);

@@ -1763,9 +1817,15 @@ public class ParsingPackageUtils {
            // Add a hidden app detail activity to normal apps which forwards user to App Details
            // page.
            ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
            // Backwards-compat, assume success
            if (a.isError()) {
                // Error should be impossible here, as the only failure case as of SDK R is a
                // string validation error on a constant ":app_details" string passed in by the
                // parsing code itself. For this reason, this is just a hard failure instead of
                // deferred.
                return input.error(a);
            }

            pkg.addActivity(a.getResult());
            a.ignoreError();
        }

        if (hasActivityOrder) {
@@ -2122,18 +2182,14 @@ public class ParsingPackageUtils {
    private static ParseResult<ParsedActivity> generateAppDetailsHiddenActivity(ParseInput input,
            ParsingPackage pkg) {
        String packageName = pkg.getPackageName();
        ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
        ParseResult<String> result = ComponentParseUtils.buildTaskAffinityName(
                packageName, packageName, ":app_details", input);

        String taskAffinity;
        if (taskAffinityResult.isSuccess()) {
            taskAffinity = taskAffinityResult.getResult();
        } else {
            // Backwards-compat, do not fail
            taskAffinity = null;
            taskAffinityResult.ignoreError();
        if (result.isError()) {
            return input.error(result);
        }

        String taskAffinity = result.getResult();

        // Build custom App Details activity info instead of parsing it from xml
        return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
                pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
@@ -2688,7 +2744,8 @@ public class ParsingPackageUtils {
    public interface Callback {
        boolean hasFeature(String feature);

        ParsingPackage startParsingPackage(String packageName, String baseCodePath, String codePath,
        ParsingPackage startParsingPackage(@NonNull String packageName,
                @NonNull String baseCodePath, @NonNull String codePath,
                @NonNull TypedArray manifestArray, boolean isCoreApp);
    }
}
+4 −5
Original line number Diff line number Diff line
@@ -179,13 +179,12 @@ public class ParsedActivityUtils {

            ParseResult<String> affinityNameResult = ComponentParseUtils.buildTaskAffinityName(
                    packageName, pkg.getTaskAffinity(), taskAffinity, input);
            if (affinityNameResult.isSuccess()) {
                activity.taskAffinity = affinityNameResult.getResult();
            } else {
                // Backwards-compat, ignore error
                affinityNameResult.ignoreError();
            if (affinityNameResult.isError()) {
                return input.error(affinityNameResult);
            }

            activity.taskAffinity = affinityNameResult.getResult();

            boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
            if (visibleToEphemeral) {
                activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
+12 −3
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.PatternMatcher;
import android.text.TextUtils;
import android.util.Slog;
import android.util.TypedValue;

@@ -97,8 +96,13 @@ public class ParsedIntentInfoUtils {
                case "action": {
                    String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
                            "name");
                    if (TextUtils.isEmpty(value)) {
                    if (value == null) {
                        result = input.error("No value supplied for <android:name>");
                    } else if (value.isEmpty()) {
                        intentInfo.addAction(value);
                        // Prior to R, this was not a failure
                        result = input.deferError("No value supplied for <android:name>",
                                ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
                    } else {
                        intentInfo.addAction(value);
                        result = input.success(null);
@@ -108,8 +112,13 @@ public class ParsedIntentInfoUtils {
                case "category": {
                    String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
                            "name");
                    if (TextUtils.isEmpty(value)) {
                    if (value == null) {
                        result = input.error("No value supplied for <android:name>");
                    } else if (value.isEmpty()) {
                        intentInfo.addCategory(value);
                        // Prior to R, this was not a failure
                        result = input.deferError("No value supplied for <android:name>",
                                ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
                    } else {
                        intentInfo.addCategory(value);
                        result = input.success(null);
+7 −8
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
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.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -28,9 +31,6 @@ import android.os.Build;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;

import org.xmlpull.v1.XmlPullParserException;

@@ -83,12 +83,11 @@ class ParsedMainComponentUtils {
            ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
                    pkg.getPackageName(), pkg.getProcessName(), processName, flags,
                    separateProcesses, input);
            if (processNameResult.isSuccess()) {
                component.setProcessName(processNameResult.getResult());
            } else {
                // Backwards-compat, ignore error
                processNameResult.ignoreError();
            if (processNameResult.isError()) {
                return input.error(processNameResult);
            }

            component.setProcessName(processNameResult.getResult());
        }

        if (splitNameAttr != null) {
Loading