diff options
| author | 2020-03-06 20:33:11 +0000 | |
|---|---|---|
| committer | 2020-03-19 18:12:24 +0000 | |
| commit | e6cb3104ea432d13f167fb6cc882ee7e76ba19d9 (patch) | |
| tree | 1921d6aac8599e1512ff8038f884940d866de38f | |
| parent | f0e7d48fec203c37d60b9bf6d654e7c1317303ee (diff) | |
Allow custom toasts only if app has resumed activity
Bug linked is a vulnerability where an app starts an external activity
that it wants the user to interact with along with repeatedly posting
custom toasts with the intent of redressing the UI of the activity just
launched.
The activity that is being started is translucent, so the check for
IMPORTANCE_FOREGROUND wasn't working because activity manager was still
considering the process foreground since it had a visible activity.
Thus, changing our logic to only enable custom toasts in case the app
has a resumed activity. This has the following implications in the
cases below:
* Translucent activity on top: Block toasts
* Multi-window: Allow toasts
* Bubble: Allows when bubble is expanded (it's a resumed activity)
* SAW: Probably block, but fine since app can already do what they want
* onCreate(), onStart(), onResume(): Allow toasts
* onPause(), onStop(), onDestroy(): Block toasts
Note that custom toasts are deprecated and we haven't specified what
exactly "foreground" or "background" meant in this context, so we have
some flexibility in the implementation.
Bug: 115385786
Test: From an app start a translucent activity from another package and
then try to post a toast, verify the toast is blocked.
Test: atest ToastUITest android.widget.cts.ToastTest
android.widget.cts29.ToastTest android.server.wm.ToastTest
Change-Id: Ia434332e066f1ef2cd01e150b087f8e5117f1e63
6 files changed, 80 insertions, 21 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 69a5b35a5b12..0ee7344e0e4d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -272,6 +272,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import libcore.io.IoUtils; @@ -403,6 +404,7 @@ public class NotificationManagerService extends SystemService { private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L; private IActivityManager mAm; + private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; @@ -1905,10 +1907,10 @@ public class NotificationManagerService extends SystemService { ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, - UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm, - IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, - UserManager userManager, NotificationHistoryManager historyManager, - StatsManager statsManager) { + ActivityTaskManagerInternal atm, UsageStatsManagerInternal appUsageStats, + DevicePolicyManagerInternal dpm, IUriGrantsManager ugm, + UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager, + NotificationHistoryManager historyManager, StatsManager statsManager) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), @@ -1918,6 +1920,7 @@ public class NotificationManagerService extends SystemService { mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); mAm = am; + mAtm = atm; mUgm = ugm; mUgmInternal = ugmInternal; mPackageManager = packageManager; @@ -2104,6 +2107,7 @@ public class NotificationManagerService extends SystemService { systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService(), + LocalServices.getService(ActivityTaskManagerInternal.class), LocalServices.getService(UsageStatsManagerInternal.class), LocalServices.getService(DevicePolicyManagerInternal.class), UriGrantsManager.getService(), @@ -2835,7 +2839,9 @@ public class NotificationManagerService extends SystemService { return; } - if (callback != null && !appIsForeground && !isSystemToast && isCustom) { + boolean isAppRenderedToast = (callback != null); + if (isAppRenderedToast && isCustom && !isSystemToast + && !isPackageInForegroundForToast(pkg, callingUid)) { boolean block; long id = Binder.clearCallingIdentity(); try { @@ -2913,6 +2919,28 @@ public class NotificationManagerService extends SystemService { } } + /** + * Implementation note: Our definition of foreground for toasts is an implementation matter + * and should strike a balance between functionality and anti-abuse effectiveness. We + * currently worry about the following cases: + * <ol> + * <li>App with fullscreen activity: Allow toasts + * <li>App behind translucent activity from other app: Block toasts + * <li>App in multi-window: Allow toasts + * <li>App with expanded bubble: Allow toasts + * <li>App posting toasts on onCreate(), onStart(), onResume(): Allow toasts + * <li>App posting toasts on onPause(), onStop(), onDestroy(): Block toasts + * </ol> + * Checking if the UID has any resumed activities satisfy use-cases above. + * + * <p>Checking if {@code mActivityManager.getUidImportance(callingUid) == + * IMPORTANCE_FOREGROUND} does not work because it considers the app in foreground if it has + * any visible activities, failing case 2 in list above. + */ + private boolean isPackageInForegroundForToast(String pkg, int callingUid) { + return mAtm.hasResumedActivity(callingUid); + } + @Override public void cancelToast(String pkg, IBinder token) { Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index ca856ca7c1e6..edc87e5a4d88 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -169,6 +169,11 @@ public abstract class ActivityTaskManagerInternal { public abstract List<IBinder> getTopVisibleActivities(); /** + * Returns whether {@code uid} has any resumed activity. + */ + public abstract boolean hasResumedActivity(int uid); + + /** * Notify listeners that contents are drawn for the first time on a single task display. * * @param displayId An ID of the display on which contents are drawn. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7bacc427feb8..890b9454e6b6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6172,6 +6172,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public boolean hasResumedActivity(int uid) { + synchronized (mGlobalLock) { + final ArraySet<WindowProcessController> processes = mProcessMap.getProcesses(uid); + for (int i = 0, n = processes.size(); i < n; i++) { + final WindowProcessController process = processes.valueAt(i); + if (process.hasResumedActivity()) { + return true; + } + } + } + return false; + } + + @Override public int startActivitiesAsPackage(String packageName, @Nullable String featureId, int userId, Intent[] intents, Bundle bOptions) { Objects.requireNonNull(intents, "intents"); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 32eb932ea0ed..e5400d849e71 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -751,6 +751,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return false; } + boolean hasResumedActivity() { + for (int i = mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord activity = mActivities.get(i); + if (activity.isState(RESUMED)) { + return true; + } + } + return false; + } + void updateIntentForHeavyWeightActivity(Intent intent) { if (mActivities.isEmpty()) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 64d481a2e6a0..6315ba6b2ee2 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17,7 +17,6 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_AUTO_CANCEL; @@ -41,8 +40,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -167,6 +164,7 @@ import com.android.server.notification.NotificationManagerService.NotificationAs import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; @@ -253,6 +251,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock IActivityManager mAm; @Mock + ActivityTaskManagerInternal mAtm; + @Mock IUriGrantsManager mUgm; @Mock UriGrantsManagerInternal mUgmInternal; @@ -442,7 +442,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, - mGroupHelper, mAm, mAppUsageStats, + mGroupHelper, mAm, mAtm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); @@ -4632,8 +4632,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); - // this app is in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); + setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4651,8 +4650,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4670,8 +4668,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND); + setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), @@ -4689,8 +4686,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) .thenReturn(false); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> toast should still enqueue ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), @@ -4748,8 +4744,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4771,8 +4766,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE); - // this app is NOT in the foreground - when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE); + setAppInForegroundForToasts(mUid, false); // enqueue toast -> system toast can still be enqueued ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(), @@ -4780,6 +4774,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, mService.mToastQueue.size()); } + private void setAppInForegroundForToasts(int uid, boolean inForeground) { + int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE; + when(mActivityManager.getUidImportance(mUid)).thenReturn(importance); + when(mAtm.hasResumedActivity(uid)).thenReturn(inForeground); + } + @Test public void testOnPanelRevealedAndHidden() { int items = 5; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index e4d50c0e1786..19ff683a46a1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -65,6 +65,7 @@ import com.android.server.lights.LightsManager; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; @@ -147,6 +148,7 @@ public class RoleObserverTest extends UiServiceTestCase { mock(SnoozeHelper.class), mock(NotificationUsageStats.class), mock(AtomicFile.class), mock(ActivityManager.class), mock(GroupHelper.class), mock(IActivityManager.class), + mock(ActivityTaskManagerInternal.class), mock(UsageStatsManagerInternal.class), mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class), mock(UriGrantsManagerInternal.class), |