diff options
5 files changed, 151 insertions, 36 deletions
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index e2907224d66e..86d061cecb88 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -21,6 +21,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.Flags; import android.net.Uri; @@ -186,6 +189,18 @@ public class IntentFilter implements Parcelable { private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; /** + * An intent with action set as null used to always pass the action test during intent + * filter matching. This causes a lot of confusion and unexpected intent matches. + * Null action intents should be blocked when the intent sender application targets V or higher. + * + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long BLOCK_NULL_ACTION_INTENTS = 293560872; + + /** * The filter {@link #setPriority} value at which system high-priority * receivers are placed; that is, receivers that should execute before * application code. Applications should never use filters with this or diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 76c4feada74f..a1d75b258e33 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -463,6 +463,7 @@ import com.android.server.net.NetworkManagementInternal; import com.android.server.os.NativeTombstoneManager; import com.android.server.pm.Computer; import com.android.server.pm.Installer; +import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -15876,6 +15877,11 @@ public class ActivityManagerService extends IActivityManager.Stub registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent, resolvedType, false /*defaultOnly*/, userId); } + if (registeredReceivers != null) { + PackageManagerServiceUtils.applyNullActionBlocking( + mPlatformCompat, snapshot, registeredReceivers, + true, intent, callingUid); + } } BroadcastQueue.traceEnd(cookie); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2005b17e82a6..6a25f64b697d 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -584,8 +584,8 @@ public class ComputerEngine implements Computer { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mInjector.getCompatibility(), mComponentResolver, - list, false, intent, resolvedType, filterCallingUid); + mInjector.getCompatibility(), this, list, false, intent, + resolvedType, filterCallingUid); } } } else { @@ -609,13 +609,15 @@ public class ComputerEngine implements Computer { } list = lockedResult.result; } + PackageManagerServiceUtils.applyNullActionBlocking( + mInjector.getCompatibility(), this, list, false, intent, filterCallingUid); } if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mInjector.getCompatibility(), mComponentResolver, - list, false, originalIntent, resolvedType, filterCallingUid); + mInjector.getCompatibility(), this, list, false, originalIntent, + resolvedType, filterCallingUid); } return skipPostResolution ? list : applyPostResolutionFilter( @@ -698,20 +700,22 @@ public class ComputerEngine implements Computer { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mInjector.getCompatibility(), mComponentResolver, - list, false, intent, resolvedType, callingUid); + mInjector.getCompatibility(), this, list, false, intent, + resolvedType, callingUid); } } } else { list = queryIntentServicesInternalBody(intent, resolvedType, flags, userId, callingUid, instantAppPkgName); + PackageManagerServiceUtils.applyNullActionBlocking( + mInjector.getCompatibility(), this, list, false, intent, callingUid); } if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mInjector.getCompatibility(), mComponentResolver, - list, false, originalIntent, resolvedType, callingUid); + mInjector.getCompatibility(), this, list, false, originalIntent, + resolvedType, callingUid); } return list; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 9484d0d7b52b..5f04a0bd3264 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -27,6 +27,7 @@ import static android.system.OsConstants.O_RDWR; import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH; +import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH; import static com.android.server.LocalManagerRegistry.ManagerNotFoundException; import static com.android.server.pm.PackageInstallerSession.APP_METADATA_FILE_ACCESS_MODE; import static com.android.server.pm.PackageInstallerSession.getAppMetadataSizeLimit; @@ -115,6 +116,7 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageSplit; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.resolution.ComponentResolverApi; +import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import dalvik.system.VMRuntime; @@ -1198,9 +1200,77 @@ public class PackageManagerServiceUtils { return (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } - // Static to give access to ComputeEngine + private static ParsedMainComponent componentInfoToComponent( + ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) { + if (info instanceof ActivityInfo) { + if (isReceiver) { + return resolver.getReceiver(info.getComponentName()); + } else { + return resolver.getActivity(info.getComponentName()); + } + } else if (info instanceof ServiceInfo) { + return resolver.getService(info.getComponentName()); + } else { + // This shall never happen + throw new IllegalArgumentException("Unsupported component type"); + } + } + + /** + * Under the correct conditions, remove components if the intent has null action. + * + * `compat` and `snapshot` may be null when this method is called in ActivityManagerService + * CTS tests. The code in this method will properly avoid control flows using these arguments. + */ + public static void applyNullActionBlocking( + @Nullable PlatformCompat compat, @Nullable PackageDataSnapshot snapshot, + List componentList, boolean isReceiver, Intent intent, int filterCallingUid) { + if (ActivityManager.canAccessUnexportedComponents(filterCallingUid)) return; + + final Computer computer = (Computer) snapshot; + ComponentResolverApi resolver = null; + + final boolean enforce = android.security.Flags.blockNullActionIntents() + && (compat == null || compat.isChangeEnabledByUidInternal( + IntentFilter.BLOCK_NULL_ACTION_INTENTS, filterCallingUid)); + + for (int i = componentList.size() - 1; i >= 0; --i) { + boolean match = true; + + Object c = componentList.get(i); + if (c instanceof ResolveInfo resolveInfo) { + if (computer == null) { + // PackageManagerService is not started + return; + } + if (resolver == null) { + resolver = computer.getComponentResolver(); + } + final ParsedMainComponent comp = componentInfoToComponent( + resolveInfo.getComponentInfo(), resolver, isReceiver); + if (!comp.getIntents().isEmpty() && intent.getAction() == null) { + match = false; + } + } else if (c instanceof IntentFilter) { + if (intent.getAction() == null) { + match = false; + } + } + + if (!match) { + ActivityManagerUtils.logUnsafeIntentEvent( + UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, + filterCallingUid, intent, null, enforce); + if (enforce) { + Slog.w(TAG, "Blocking intent with null action: " + intent); + componentList.remove(i); + } + } + } + } + public static void applyEnforceIntentFilterMatching( - PlatformCompat compat, ComponentResolverApi resolver, + PlatformCompat compat, PackageDataSnapshot snapshot, List<ResolveInfo> resolveInfos, boolean isReceiver, Intent intent, String resolvedType, int filterCallingUid) { if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return; @@ -1208,13 +1278,19 @@ public class PackageManagerServiceUtils { // Do not enforce filter matching when the caller is system or root if (ActivityManager.canAccessUnexportedComponents(filterCallingUid)) return; + final Computer computer = (Computer) snapshot; + final ComponentResolverApi resolver = computer.getComponentResolver(); + final Printer logPrinter = DEBUG_INTENT_MATCHING ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null; - final boolean enforce = android.security.Flags.enforceIntentFilterMatch() + final boolean enforceMatch = android.security.Flags.enforceIntentFilterMatch() && compat.isChangeEnabledByUidInternal( ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid); + final boolean blockNullAction = android.security.Flags.blockNullActionIntents() + && compat.isChangeEnabledByUidInternal( + IntentFilter.BLOCK_NULL_ACTION_INTENTS, filterCallingUid); for (int i = resolveInfos.size() - 1; i >= 0; --i) { final ComponentInfo info = resolveInfos.get(i).getComponentInfo(); @@ -1224,40 +1300,53 @@ public class PackageManagerServiceUtils { continue; } - final ParsedMainComponent comp; - if (info instanceof ActivityInfo) { - if (isReceiver) { - comp = resolver.getReceiver(info.getComponentName()); - } else { - comp = resolver.getActivity(info.getComponentName()); - } - } else if (info instanceof ServiceInfo) { - comp = resolver.getService(info.getComponentName()); - } else { - // This shall never happen - throw new IllegalArgumentException("Unsupported component type"); - } + final ParsedMainComponent comp = componentInfoToComponent(info, resolver, isReceiver); if (comp == null || comp.getIntents().isEmpty()) { continue; } - boolean match = false; - for (int j = 0, size = comp.getIntents().size(); j < size; ++j) { - IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter(); - if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) { - match = true; - break; + Boolean match = null; + + if (intent.getAction() == null) { + ActivityManagerUtils.logUnsafeIntentEvent( + UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, + filterCallingUid, intent, resolvedType, enforceMatch && blockNullAction); + if (blockNullAction) { + // Skip intent filter matching if blocking null action + match = false; } } - if (!match) { + + if (match == null) { + // Check if any intent filter matches + for (int j = 0, size = comp.getIntents().size(); j < size; ++j) { + IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter(); + if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) { + match = true; + break; + } + } + } + + // At this point, the value `match` has the following states: + // null : Intent does not match any intent filter + // false: Null action intent detected AND blockNullAction == true + // true : The intent matches at least one intent filter + + if (match == null) { ActivityManagerUtils.logUnsafeIntentEvent( UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH, - filterCallingUid, intent, resolvedType, enforce); + filterCallingUid, intent, resolvedType, enforceMatch); + match = false; + } + + if (!match) { + // All non-matching intents has to be marked accordingly if (android.security.Flags.enforceIntentFilterMatch()) { intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); } - if (enforce) { + if (enforceMatch) { Slog.w(TAG, "Intent does not match component's intent filter: " + intent); Slog.w(TAG, "Access blocked: " + comp.getComponentName()); if (DEBUG_INTENT_MATCHING) { @@ -1271,7 +1360,6 @@ public class PackageManagerServiceUtils { } } - /** * Do NOT use for intent resolution filtering. That should be done with * {@link DomainVerificationManagerInternal#filterToApprovedApp(Intent, List, int, Function)}. diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index b664e39cfd1c..309a4481e9de 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -458,7 +458,7 @@ final class ResolveIntentHelper { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mPlatformCompat, componentResolver, list, true, intent, + mPlatformCompat, computer, list, true, intent, resolvedType, filterCallingUid); } } @@ -479,12 +479,14 @@ final class ResolveIntentHelper { list = result; } } + PackageManagerServiceUtils.applyNullActionBlocking( + mPlatformCompat, computer, list, true, intent, filterCallingUid); } if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - mPlatformCompat, componentResolver, + mPlatformCompat, computer, list, true, originalIntent, resolvedType, filterCallingUid); } |