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

Commit 0c9257b7 authored by Patrick Baumann's avatar Patrick Baumann
Browse files

Adds app enumeration support for all components

This change adds consideration for more than just activities when
computing matches between packages for app enumeration / filtering.

It also changes the failure logic when parsing a package to allow for a
queries intent tag that contains no action, but a scheme or one that
contains no data tag but one action (or both). Previously it would have
been impossible to enumerate an app purely based on the authority of one
of its providers.

Bug: 136675067
Test: adb shell device_config put package_manager_service package_query_filtering_enabled true && atest AppEnumerationTests
Test: atest AppsFilterTest
Change-Id: I07bb449e78fb79a2ed61f75b37e582e0f3467a2d
parent c045ff6c
Loading
Loading
Loading
Loading
+36 −14
Original line number Diff line number Diff line
@@ -4070,32 +4070,54 @@ public class PackageParser {
                        intentInfo, outError)) {
                    return false;
                }
                Intent intent = new Intent();
                if (intentInfo.countActions() != 1) {
                    outError[0] = "intent tags must contain exactly one action.";

                Uri data = null;
                String dataType = null;
                String host = "";
                final int numActions = intentInfo.countActions();
                final int numSchemes = intentInfo.countDataSchemes();
                final int numTypes = intentInfo.countDataTypes();
                final int numHosts = intentInfo.getHosts().length;
                if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
                    outError[0] = "intent tags must contain either an action or data.";
                    return false;
                }
                intent.setAction(intentInfo.getAction(0));
                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
                    intent.addCategory(intentInfo.getCategory(i));
                if (numActions > 1) {
                    outError[0] = "intent tag may have at most one action.";
                    return false;
                }
                Uri data = null;
                String dataType = null;
                if (intentInfo.countDataTypes() > 1) {
                if (numTypes > 1) {
                    outError[0] = "intent tag may have at most one data type.";
                    return false;
                }
                if (intentInfo.countDataSchemes() > 1) {
                if (numSchemes > 1) {
                    outError[0] = "intent tag may have at most one data scheme.";
                    return false;
                }
                if (intentInfo.countDataTypes() == 1) {
                    data = Uri.fromParts(intentInfo.getDataType(0), "", null);
                if (numHosts > 1) {
                    outError[0] = "intent tag may have at most one data host.";
                    return false;
                }
                Intent intent = new Intent();
                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
                    intent.addCategory(intentInfo.getCategory(i));
                }
                if (intentInfo.countDataSchemes() == 1) {
                    dataType = intentInfo.getDataScheme(0);
                if (numHosts == 1) {
                    host = intentInfo.getHosts()[0];
                }
                if (numSchemes == 1) {
                    data = new Uri.Builder()
                            .scheme(intentInfo.getDataScheme(0))
                            .authority(host)
                            .build();
                }
                if (numTypes == 1) {
                    dataType = intentInfo.getDataType(0);
                }
                intent.setDataAndType(data, dataType);
                if (numActions == 1) {
                    intent.setAction(intentInfo.getAction(0));
                }
                owner.mQueriesIntents = ArrayUtils.add(owner.mQueriesIntents, intent);
            } else if (parser.getName().equals("package")) {
                final TypedArray sa = res.obtainAttributes(parser,
+86 −18
Original line number Diff line number Diff line
@@ -16,13 +16,18 @@

package com.android.server.pm;

import static android.content.pm.PackageParser.Component;
import static android.content.pm.PackageParser.IntentInfo;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;

import android.Manifest;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
@@ -38,6 +43,7 @@ import com.android.server.FgThread;
import com.android.server.compat.PlatformCompat;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -55,13 +61,13 @@ class AppsFilter {

    // Forces filtering logic to run for debug purposes.
    // STOPSHIP (b/136675067): should be false after development is complete
    private static final boolean DEBUG_RUN_WHEN_DISABLED = true;
    private static final boolean DEBUG_RUN_WHEN_DISABLED = false;

    // Logs all filtering instead of enforcing
    private static final boolean DEBUG_ALLOW_ALL = false;

    @SuppressWarnings("ConstantExpression")
    private static final boolean DEBUG_LOGGING = false | DEBUG_RUN_WHEN_DISABLED | DEBUG_ALLOW_ALL;
    private static final boolean DEBUG_LOGGING = false | DEBUG_ALLOW_ALL;

    /**
     * This contains a list of packages that are implicitly queryable because another app explicitly
@@ -200,23 +206,43 @@ class AppsFilter {
            return false;
        }
        for (Intent intent : querying.mQueriesIntents) {
            for (PackageParser.Activity activity : potentialTarget.activities) {
                if (activity.intents != null) {
                    for (PackageParser.ActivityIntentInfo filter : activity.intents) {
                        if (matches(intent, filter)) {
            if (matches(intent, potentialTarget.providers, potentialTarget.activities,
                    potentialTarget.services, potentialTarget.receivers)) {
                return true;
            }
        }
        return false;
    }

    private static boolean matches(Intent intent,
            ArrayList<PackageParser.Provider> providerList,
            ArrayList<? extends Component<? extends IntentInfo>>... componentLists) {
        for (int p = providerList.size() - 1; p >= 0; p--) {
            PackageParser.Provider provider = providerList.get(p);
            final ProviderInfo providerInfo = provider.info;
            final Uri data = intent.getData();
            if ("content".equalsIgnoreCase(intent.getScheme())
                    && data != null
                    && providerInfo.authority.equalsIgnoreCase(data.getAuthority())) {
                return true;
            }
        }
        return false;
    }

    /** Returns true if the given intent matches the given filter. */
    private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) {
        return filter.match(intent.getAction(), intent.getType(), intent.getScheme(),
                intent.getData(), intent.getCategories(), "AppsFilter") > 0;
        for (int l = componentLists.length - 1; l >= 0; l--) {
            ArrayList<? extends Component<? extends IntentInfo>> components = componentLists[l];
            for (int c = components.size() - 1; c >= 0; c--) {
                Component<? extends IntentInfo> component = components.get(c);
                ArrayList<? extends IntentInfo> intents = component.intents;
                for (int i = intents.size() - 1; i >= 0; i--) {
                    IntentFilter intentFilter = intents.get(i);
                    if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
                            intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
@@ -328,9 +354,15 @@ class AppsFilter {
            PackageSetting targetPkgSetting, int userId) {
        final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
        if (!featureEnabled && !DEBUG_RUN_WHEN_DISABLED) {
            if (DEBUG_LOGGING) {
                Slog.d(TAG, "filtering disabled; skipped");
            }
            return false;
        }
        if (callingUid < Process.FIRST_APPLICATION_UID) {
            if (DEBUG_LOGGING) {
                Slog.d(TAG, "filtering skipped; " + callingUid + " is system");
            }
            return false;
        }
        if (callingSetting == null) {
@@ -376,14 +408,13 @@ class AppsFilter {
        }
        if (mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
            if (DEBUG_LOGGING) {
                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
                        + targetPkgSetting.name + (DEBUG_ALLOW_ALL ? " ALLOWED" : "BLOCKED"));
                log(callingPkgSetting, targetPkgSetting,
                        DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED");
            }
            return !DEBUG_ALLOW_ALL;
        } else {
            if (DEBUG_LOGGING) {
                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
                        + targetPkgSetting.name + " DISABLED");
                log(callingPkgSetting, targetPkgSetting, "DISABLED");
            }
            return false;
        }
@@ -397,38 +428,65 @@ class AppsFilter {
        // This package isn't technically installed and won't be written to settings, so we can
        // treat it as filtered until it's available again.
        if (targetPkg == null) {
            if (DEBUG_LOGGING) {
                Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
            }
            return true;
        }
        final String targetName = targetPkg.packageName;
        if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "caller pre-R");
            }
            return false;
        }
        if (isImplicitlyQueryableSystemApp(targetPkgSetting)) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "implicitly queryable sys");
            }
            return false;
        }
        if (targetPkg.mForceQueryable) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "manifest forceQueryable");
            }
            return false;
        }
        if (mForceQueryable.contains(targetName)) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "whitelist forceQueryable");
            }
            return false;
        }
        if (mQueriesViaPackage.containsKey(callingName)
                && mQueriesViaPackage.get(callingName).contains(
                targetName)) {
            // the calling package has explicitly declared the target package; allow
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "queries package");
            }
            return false;
        } else if (mQueriesViaIntent.containsKey(callingName)
                && mQueriesViaIntent.get(callingName).contains(targetName)) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "queries intent");
            }
            return false;
        }
        if (mImplicitlyQueryable.get(userId) != null
                && mImplicitlyQueryable.get(userId).containsKey(callingName)
                && mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) {
            if (DEBUG_LOGGING) {
                log(callingPkgSetting, targetPkgSetting, "implicitly queryable for user");
            }
            return false;
        }
        if (callingPkgSetting.pkg.instrumentation.size() > 0) {
            for (int i = 0, max = callingPkgSetting.pkg.instrumentation.size(); i < max; i++) {
                if (callingPkgSetting.pkg.instrumentation.get(i).info.targetPackage == targetName) {
                    if (DEBUG_LOGGING) {
                        log(callingPkgSetting, targetPkgSetting, "instrumentation");
                    }
                    return false;
                }
            }
@@ -437,6 +495,9 @@ class AppsFilter {
            if (mPermissionManager.checkPermission(
                    Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId)
                    == PackageManager.PERMISSION_GRANTED) {
                if (DEBUG_LOGGING) {
                    log(callingPkgSetting, targetPkgSetting, "permission");
                }
                return false;
            }
        } catch (RemoteException e) {
@@ -445,6 +506,13 @@ class AppsFilter {
        return true;
    }

    private static void log(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting,
            String description) {
        Slog.wtf(TAG,
                "interaction: " + callingPkgSetting.name + " -> " + targetPkgSetting.name + " "
                        + description);
    }

    private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) {
        return targetPkgSetting.isSystem() && (mSystemAppsQueryable
                || mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName));