From 91007ba8b97788477fc238cc781c54fc82e830e5 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Wed, 4 Jan 2023 05:01:39 +0000 Subject: [PATCH 01/61] Revert "[RESTRICT AUTOMERGE] Trim the activity info of another uid if no privilege" This reverts commit fa8d6362348738284b3f33a13e1fa5cdd0af67b2. Reason for revert: apps crashed due to the top activity info trimmed Bug: 264269392 263434196 263438172 Change-Id: I57d37649acb31bd93bd5aa10507f548cd77fc8f2 (cherry picked from commit b37e4e7e6f465c4b6a291be6c65587dbd75b4ae4) Merged-In: I57d37649acb31bd93bd5aa10507f548cd77fc8f2 --- .../com/android/server/wm/AppTaskImpl.java | 2 +- .../com/android/server/wm/RecentTasks.java | 8 ++---- .../com/android/server/wm/RunningTasks.java | 4 --- .../core/java/com/android/server/wm/Task.java | 21 --------------- .../android/server/wm/RecentTasksTest.java | 27 ++++--------------- 5 files changed, 8 insertions(+), 54 deletions(-) diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 3c9adfb9c4bd..558939611905 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -84,7 +84,7 @@ public ActivityManager.RecentTaskInfo getTaskInfo() { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } return mService.getRecentTasks().createRecentTaskInfo(task, - false /* stripExtras */, true /* getTasksAllowed */); + false /* stripExtras */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 1a3d621794d3..dca0bbda78cf 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -974,7 +974,7 @@ private ArrayList getRecentTasksImpl(int maxNum, continue; } - res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed)); + res.add(createRecentTaskInfo(task, true /* stripExtras */)); } return res; } @@ -1890,8 +1890,7 @@ void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { /** * Creates a new RecentTaskInfo from a Task. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras, - boolean getTasksAllowed) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) { final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); // If the recent Task is detached, we consider it will be re-attached to the default // TaskDisplayArea because we currently only support recent overview in the default TDA. @@ -1903,9 +1902,6 @@ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; rti.lastSnapshotData.set(tr.mLastTaskSnapshotData); - if (!getTasksAllowed) { - Task.trimIneffectiveInfo(tr, rti); - } // Fill in organized child task info for the task created by organizer. if (tr.mCreatedByOrganizer) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 7acd5152c291..9864297de529 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -150,10 +150,6 @@ private RunningTaskInfo createRunningTaskInfo(Task task) { task.fillTaskInfo(rti, !mKeepIntentExtra); // Fill in some deprecated values rti.id = rti.taskId; - - if (!mAllowed) { - Task.trimIneffectiveInfo(task, rti); - } return rti; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 84cdd1cbbebd..7e9f8c388f0a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3487,27 +3487,6 @@ void fillTaskInfo(TaskInfo info, boolean stripExtras, @Nullable TaskDisplayArea info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null; } - /** - * Removes the activity info if the activity belongs to a different uid, which is - * different from the app that hosts the task. - */ - static void trimIneffectiveInfo(Task task, TaskInfo info) { - final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing, - false /* traverseTopToBottom */); - final int baseActivityUid = - baseActivity != null ? baseActivity.getUid() : task.effectiveUid; - - if (info.topActivityInfo != null - && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { - info.topActivity = null; - info.topActivityInfo = null; - } - - if (task.effectiveUid != baseActivityUid) { - info.baseActivity = null; - } - } - @Nullable PictureInPictureParams getPictureInPictureParams() { return getPictureInPictureParams(getTopMostTask()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index f5e16a903a96..284728397c9f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -30,7 +30,6 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.os.Process.NOBODY_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -1196,35 +1195,21 @@ public void testLastSnapshotData_notSet() { @Test public void testCreateRecentTaskInfo_detachedTask() { - final Task task = createTaskBuilder(".Task").build(); - new ActivityBuilder(mSupervisor.mService) - .setTask(task) - .setUid(NOBODY_UID) - .setComponent(new ComponentName("com.foo", ".BarActivity")) - .build(); + final Task task = createTaskBuilder(".Task").setCreateActivity(true).build(); final TaskDisplayArea tda = task.getDisplayArea(); assertTrue(task.isAttached()); assertTrue(task.supportsMultiWindow()); - RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - false /* getTasksAllowed */); - - assertTrue(info.topActivity == null); - assertTrue(info.topActivityInfo == null); - assertTrue(info.baseActivity == null); - // The task can be put in split screen even if it is not attached now. task.removeImmediately(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); @@ -1234,8 +1219,7 @@ public void testCreateRecentTaskInfo_detachedTask() { doReturn(false).when(tda).supportsNonResizableMultiWindow(); doReturn(false).when(task).isResizeable(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertFalse(info.supportsMultiWindow); assertFalse(info.supportsSplitScreenMultiWindow); @@ -1244,8 +1228,7 @@ public void testCreateRecentTaskInfo_detachedTask() { // the device supports it. doReturn(true).when(tda).supportsNonResizableMultiWindow(); - info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, - true /* getTasksAllowed */); + info = mRecentTasks.createRecentTaskInfo(task, true); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); From 3ef763acd5bff3f89c751ac465f4d1eb5e123d37 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 16 May 2022 15:28:24 -0400 Subject: [PATCH 02/61] Move service initialization Occasionally ILockSettings can fail to be initialized otherwise Fixes: 232714129 Test: boot (and eventually bootstress/reboot-long) Change-Id: I2f9f9bdba37f4ebfaea56c1a6662f0474ae8a002 Merged-In: I2f9f9bdba37f4ebfaea56c1a6662f0474ae8a002 (cherry picked from commit 8e278543bd290d4b6c417758554d6dee93a4fe74) (cherry picked from commit d262fa6af707a18226c5a586ae2704ab0a9907bf) Merged-In: I2f9f9bdba37f4ebfaea56c1a6662f0474ae8a002 --- .../server/notification/NotificationManagerService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d0a551f43686..b7fdaaa05f33 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1976,7 +1976,6 @@ public synchronized void onStrongAuthRequiredChanged(int userId) { } } - private LockPatternUtils mLockPatternUtils; private StrongAuthTracker mStrongAuthTracker; public NotificationManagerService(Context context) { @@ -2193,7 +2192,6 @@ void init(WorkerHandler handler, RankingHandler rankingHandler, ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mUiHandler = new Handler(UiThread.get().getLooper()); - mLockPatternUtils = new LockPatternUtils(getContext()); mStrongAuthTracker = new StrongAuthTracker(getContext()); String[] extractorNames; try { @@ -2662,7 +2660,7 @@ void onBootPhase(int phase, Looper mainLooper) { bubbsExtractor.setShortcutHelper(mShortcutHelper); } registerNotificationPreferencesPullers(); - mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker); + new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. From 5040a36b3540a6a924f9f29ef8088eae56f9fa22 Mon Sep 17 00:00:00 2001 From: Wenhao Wang Date: Tue, 30 Aug 2022 11:09:46 -0700 Subject: [PATCH 03/61] Enable user graularity for lockdown mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NotificationManagerService registers a LockPatternUtils.StrongAuthTracker to observe the StrongAuth changes of every user. More specifically, it’s the STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN flag. Via this flag, NotificationManagerService can perform the following operations when the user enter or exit lockdown mode: Enter lockdown: 1. Remove all the notifications belonging to the user. 2. Set the local flag to indicate the lockdown is on for the user. The local flag will suppress the user's notifications on the post, remove and update functions. Exit lockdown: 1. Clear the local flag to indicate the lockdown is off for the user. 2. Repost the user’s notifications (suppressed during lockdown mode). The CL also updates corresponding tests. Bug: 173721373 Bug: 250743174 Test: atest NotificationManagerServiceTest Test: atest NotificationListenersTest Ignore-AOSP-First: pending fix for a security issue. Change-Id: I4f30e56550729db7d673a92d2a1250509713f36d Merged-In: I4f30e56550729db7d673a92d2a1250509713f36d (cherry picked from commit de3b12fca23178d8c821058261572449b67d5967) (cherry picked from commit 0b56ec9aa245f7bbdf065a4b33b5ef00a558dbe4) Merged-In: I4f30e56550729db7d673a92d2a1250509713f36d --- .../NotificationManagerService.java | 72 +++++---- .../NotificationListenersTest.java | 148 ++++++++++++------ .../NotificationManagerServiceTest.java | 68 +++++++- 3 files changed, 203 insertions(+), 85 deletions(-) diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b7fdaaa05f33..90ae22aa25f5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1944,34 +1944,39 @@ private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } - public boolean isInLockDownMode() { - return mIsInLockDownMode; + // Return whether the user is in lockdown mode. + // If the flag is not set, we assume the user is not in lockdown. + public boolean isInLockDownMode(int userId) { + return mUserInLockDownMode.get(userId, false); } @Override public synchronized void onStrongAuthRequiredChanged(int userId) { boolean userInLockDownModeNext = containsFlag(getStrongAuthForUser(userId), STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - mUserInLockDownMode.put(userId, userInLockDownModeNext); - boolean isInLockDownModeNext = mUserInLockDownMode.indexOfValue(true) != -1; - if (mIsInLockDownMode == isInLockDownModeNext) { + // Nothing happens if the lockdown mode of userId keeps the same. + if (userInLockDownModeNext == isInLockDownMode(userId)) { return; } - if (isInLockDownModeNext) { - cancelNotificationsWhenEnterLockDownMode(); + // When the lockdown mode is changed, we perform the following steps. + // If the userInLockDownModeNext is true, all the function calls to + // notifyPostedLocked and notifyRemovedLocked will not be executed. + // The cancelNotificationsWhenEnterLockDownMode calls notifyRemovedLocked + // and postNotificationsWhenExitLockDownMode calls notifyPostedLocked. + // So we shall call cancelNotificationsWhenEnterLockDownMode before + // we set mUserInLockDownMode as true. + // On the other hand, if the userInLockDownModeNext is false, we shall call + // postNotificationsWhenExitLockDownMode after we put false into mUserInLockDownMode + if (userInLockDownModeNext) { + cancelNotificationsWhenEnterLockDownMode(userId); } - // When the mIsInLockDownMode is true, both notifyPostedLocked and - // notifyRemovedLocked will be dismissed. So we shall call - // cancelNotificationsWhenEnterLockDownMode before we set mIsInLockDownMode - // as true and call postNotificationsWhenExitLockDownMode after we set - // mIsInLockDownMode as false. - mIsInLockDownMode = isInLockDownModeNext; + mUserInLockDownMode.put(userId, userInLockDownModeNext); - if (!isInLockDownModeNext) { - postNotificationsWhenExitLockDownMode(); + if (!userInLockDownModeNext) { + postNotificationsWhenExitLockDownMode(userId); } } } @@ -9381,11 +9386,14 @@ private void unhideNotificationsForPackages(@NonNull String[] pkgs, } } - private void cancelNotificationsWhenEnterLockDownMode() { + private void cancelNotificationsWhenEnterLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); + if (rec.getUser().getIdentifier() != userId) { + continue; + } mListeners.notifyRemovedLocked(rec, REASON_CANCEL_ALL, rec.getStats()); } @@ -9393,14 +9401,23 @@ private void cancelNotificationsWhenEnterLockDownMode() { } } - private void postNotificationsWhenExitLockDownMode() { + private void postNotificationsWhenExitLockDownMode(int userId) { synchronized (mNotificationLock) { int numNotifications = mNotificationList.size(); + // Set the delay to spread out the burst of notifications. + long delay = 0; for (int i = 0; i < numNotifications; i++) { NotificationRecord rec = mNotificationList.get(i); - mListeners.notifyPostedLocked(rec, rec); + if (rec.getUser().getIdentifier() != userId) { + continue; + } + mHandler.postDelayed(() -> { + synchronized (mNotificationLock) { + mListeners.notifyPostedLocked(rec, rec); + } + }, delay); + delay += 20; } - } } @@ -9598,12 +9615,15 @@ private static String callStateToString(int state) { * notifications visible to the given listener. */ @GuardedBy("mNotificationLock") - private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { + NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) { final int N = mNotificationList.size(); final ArrayList rankings = new ArrayList<>(); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); + if (isInLockDownMode(record.getUser().getIdentifier())) { + continue; + } if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) { continue; } @@ -9645,8 +9665,8 @@ private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo inf rankings.toArray(new NotificationListenerService.Ranking[0])); } - boolean isInLockDownMode() { - return mStrongAuthTracker.isInLockDownMode(); + boolean isInLockDownMode(int userId) { + return mStrongAuthTracker.isInLockDownMode(userId); } boolean hasCompanionDevice(ManagedServiceInfo info) { @@ -10702,7 +10722,7 @@ public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) { @GuardedBy("mNotificationLock") void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -10803,7 +10823,7 @@ private void updateUriPermissionsForActiveNotificationsLocked( @GuardedBy("mNotificationLock") public void notifyRemovedLocked(NotificationRecord r, int reason, NotificationStats notificationStats) { - if (isInLockDownMode()) { + if (isInLockDownMode(r.getUser().getIdentifier())) { return; } @@ -10852,10 +10872,6 @@ public void notifyRemovedLocked(NotificationRecord r, int reason, */ @GuardedBy("mNotificationLock") public void notifyRankingUpdateLocked(List changedHiddenNotifications) { - if (isInLockDownMode()) { - return; - } - boolean isHiddenRankingUpdate = changedHiddenNotifications != null && changedHiddenNotifications.size() > 0; // TODO (b/73052211): if the ranking update changed the notification type, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 81730e6d7b32..f476d6f07316 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -69,7 +69,6 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.util.List; public class NotificationListenersTest extends UiServiceTestCase { @@ -381,63 +380,112 @@ public void testBroadcastUsers() { @Test public void testNotifyPostedLockedInLockdownMode() { - NotificationRecord r = mock(NotificationRecord.class); - NotificationRecord old = mock(NotificationRecord.class); - - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - mListeners.notifyPostedLocked(r, old, true); - mListeners.notifyPostedLocked(r, old, false); - verify(r, atLeast(2)).getSbn(); - - // in the lockdown mode - reset(r); - reset(old); - when(mNm.isInLockDownMode()).thenReturn(true); - mListeners.notifyPostedLocked(r, old, true); - mListeners.notifyPostedLocked(r, old, false); - verify(r, never()).getSbn(); - } - - @Test - public void testnotifyRankingUpdateLockedInLockdownMode() { - List chn = mock(List.class); - - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - mListeners.notifyRankingUpdateLocked(chn); - verify(chn, atLeast(1)).size(); - - // in the lockdown mode - reset(chn); - when(mNm.isInLockDownMode()).thenReturn(true); - mListeners.notifyRankingUpdateLocked(chn); - verify(chn, never()).size(); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationRecord old0 = mock(NotificationRecord.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationRecord old1 = mock(NotificationRecord.class); + UserHandle uh1 = mock(UserHandle.class); + + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(old0); + reset(r1); + reset(old1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + + mListeners.notifyPostedLocked(r0, old0, true); + mListeners.notifyPostedLocked(r0, old0, false); + verify(r0, never()).getSbn(); + + mListeners.notifyPostedLocked(r1, old1, true); + mListeners.notifyPostedLocked(r1, old1, false); + verify(r1, atLeast(2)).getSbn(); } @Test public void testNotifyRemovedLockedInLockdownMode() throws NoSuchFieldException { - NotificationRecord r = mock(NotificationRecord.class); - NotificationStats rs = mock(NotificationStats.class); + NotificationRecord r0 = mock(NotificationRecord.class); + NotificationStats rs0 = mock(NotificationStats.class); + UserHandle uh0 = mock(UserHandle.class); + + NotificationRecord r1 = mock(NotificationRecord.class); + NotificationStats rs1 = mock(NotificationStats.class); + UserHandle uh1 = mock(UserHandle.class); + StatusBarNotification sbn = mock(StatusBarNotification.class); FieldSetter.setField(mNm, NotificationManagerService.class.getDeclaredField("mHandler"), mock(NotificationManagerService.WorkerHandler.class)); - // before the lockdown mode - when(mNm.isInLockDownMode()).thenReturn(false); - when(r.getSbn()).thenReturn(sbn); - mListeners.notifyRemovedLocked(r, 0, rs); - mListeners.notifyRemovedLocked(r, 0, rs); - verify(r, atLeast(2)).getSbn(); - - // in the lockdown mode - reset(r); - reset(rs); - when(mNm.isInLockDownMode()).thenReturn(true); - when(r.getSbn()).thenReturn(sbn); - mListeners.notifyRemovedLocked(r, 0, rs); - mListeners.notifyRemovedLocked(r, 0, rs); - verify(r, never()).getSbn(); + // Neither user0 and user1 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(false); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, atLeast(2)).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); + + // Reset + reset(r0); + reset(rs0); + reset(r1); + reset(rs1); + + // Only user 0 is in the lockdown mode + when(r0.getUser()).thenReturn(uh0); + when(uh0.getIdentifier()).thenReturn(0); + when(mNm.isInLockDownMode(0)).thenReturn(true); + when(r0.getSbn()).thenReturn(sbn); + + when(r1.getUser()).thenReturn(uh1); + when(uh1.getIdentifier()).thenReturn(1); + when(mNm.isInLockDownMode(1)).thenReturn(false); + when(r1.getSbn()).thenReturn(sbn); + + mListeners.notifyRemovedLocked(r0, 0, rs0); + mListeners.notifyRemovedLocked(r0, 0, rs0); + verify(r0, never()).getSbn(); + + mListeners.notifyRemovedLocked(r1, 0, rs1); + mListeners.notifyRemovedLocked(r1, 0, rs1); + verify(r1, atLeast(2)).getSbn(); } } 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 c8e88c5a401b..c45fb019e4c7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -159,6 +159,7 @@ import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerFilter; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.ZenPolicy; @@ -194,6 +195,7 @@ import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; @@ -345,6 +347,9 @@ private static class TestableNotificationManagerService extends NotificationMana @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; + @Nullable + Boolean mIsVisibleToListenerReturnValue = null; + TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); @@ -419,6 +424,19 @@ interface NotificationAssistantAccessGrantedCallback { void onGranted(ComponentName assistant, int userId, boolean granted, boolean userSet); } + protected void setIsVisibleToListenerReturnValue(boolean value) { + mIsVisibleToListenerReturnValue = value; + } + + @Override + boolean isVisibleToListener(StatusBarNotification sbn, int notificationType, + ManagedServiceInfo listener) { + if (mIsVisibleToListenerReturnValue != null) { + return mIsVisibleToListenerReturnValue; + } + return super.isVisibleToListener(sbn, notificationType, listener); + } + class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { @@ -8514,10 +8532,10 @@ public void testStrongAuthTracker_isInLockDownMode() { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); - mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(mContext.getUserId()); mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertFalse(mStrongAuthTracker.isInLockDownMode()); + assertFalse(mStrongAuthTracker.isInLockDownMode(mContext.getUserId())); } @Test @@ -8533,8 +8551,8 @@ public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() { // when entering the lockdown mode, cancel the 2 notifications. mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); - assertTrue(mStrongAuthTracker.isInLockDownMode()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); // the notifyRemovedLocked function is called twice due to REASON_LOCKDOWN. ArgumentCaptor captor = ArgumentCaptor.forClass(Integer.class); @@ -8543,9 +8561,45 @@ public void testCancelAndPostNotificationsWhenEnterAndExitLockDownMode() { // exit lockdown mode. mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); - mStrongAuthTracker.onStrongAuthRequiredChanged(mContext.getUserId()); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); // the notifyPostedLocked function is called twice. - verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + verify(mWorkerHandler, times(2)).postDelayed(any(Runnable.class), anyLong()); + //verify(mListeners, times(2)).notifyPostedLocked(any(), any()); + } + + @Test + public void testMakeRankingUpdateLockedInLockDownMode() { + // post 2 notifications from a same package + NotificationRecord pkgA = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 0), mTestNotificationChannel); + mService.addNotification(pkgA); + NotificationRecord pkgB = new NotificationRecord(mContext, + generateSbn("a", 1000, 9, 1), mTestNotificationChannel); + mService.addNotification(pkgB); + + mService.setIsVisibleToListenerReturnValue(true); + NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); + + // when only user 0 entering the lockdown mode, its notification will be suppressed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertTrue(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(1, nru.getRankingMap().getOrderedKeys().length); + + // User 0 exits lockdown mode. Its notification will be resumed. + mStrongAuthTracker.setGetStrongAuthForUserReturnValue(0); + mStrongAuthTracker.onStrongAuthRequiredChanged(0); + assertFalse(mStrongAuthTracker.isInLockDownMode(0)); + assertFalse(mStrongAuthTracker.isInLockDownMode(1)); + + nru = mService.makeRankingUpdateLocked(null); + assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } } From c0bf2f8b70027f03fb0136d4627863389a419090 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Mon, 26 Sep 2022 20:37:33 +0100 Subject: [PATCH 04/61] Reconcile WorkSource parcel and unparcel code. Prior to this CL, WorkSources would Parcel their list of WorkChains as -1 if null, or the size of the list followed by the list itself if non-null. When reading it back in, on the other hand, they would check if the size was positive, and only then read the list from the Parcel. This works for all cases except when the WorkSource has an empty but non-null list of WorkChains as the list would get written to the parcel, but then never read on the other side. If parceling a list was a no-op when empty this wouldn't be an issue, but it must write at least its size into the parcel to know how many elements to extract. In the empty list case, this single element is left unread as the size is not positive which essentially corrupts any future items read from that same parcelable. Bug: 220302519 Test: atest android.security.cts.WorkSourceTest#testWorkChainParceling Change-Id: I2fec40dfced420ca38e717059b0e95ee8ef9946a (cherry picked from commit 266b3bddcf14d448c0972db64b42950f76c759e3) Merged-In: I2fec40dfced420ca38e717059b0e95ee8ef9946a --- core/java/android/os/WorkSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 6588b5748d09..cc7cf241e3c9 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -128,7 +128,7 @@ public WorkSource(int uid, @NonNull String packageName) { mNames = in.createStringArray(); int numChains = in.readInt(); - if (numChains > 0) { + if (numChains >= 0) { mChains = new ArrayList<>(numChains); in.readParcelableList(mChains, WorkChain.class.getClassLoader()); } else { From ec76629b22c1acc4edc1bbc2aa1995447431e5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Budnik?= Date: Tue, 4 Oct 2022 15:52:30 +0000 Subject: [PATCH 05/61] Enforce MediaButtonReceiver extracted component name matches session package name This change makes sure that the extracted component name in a MediaButtonReceiverHolder matches the Media Session owner's package name. This avoids incorrectly routing media button events and potential security issues. Bug: 244312001 Bug: 238177121 Test: atest CtsMediaBetterTogetherTestCases Change-Id: Ifac9cf53889222e31d18c14c1e096ee68c0a346c (cherry picked from commit 185c3e252397bfa37592edbb5b2f5ae97db92eda) Merged-In: Ifac9cf53889222e31d18c14c1e096ee68c0a346c (cherry picked from commit 48c388277880e56ab5cc29e145e4d00aa383ce01) Merged-In: Ifac9cf53889222e31d18c14c1e096ee68c0a346c --- .../java/android/media/session/ISession.aidl | 2 +- .../android/media/session/MediaSession.java | 2 +- .../media/MediaButtonReceiverHolder.java | 61 +++++++++++-------- .../server/media/MediaSessionRecord.java | 5 +- .../server/media/MediaSessionService.java | 6 +- 5 files changed, 43 insertions(+), 33 deletions(-) diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 9bf126b7a875..31fb8d03c4a0 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -35,7 +35,7 @@ interface ISession { ISessionController getController(); void setFlags(int flags); void setActive(boolean active); - void setMediaButtonReceiver(in PendingIntent mbr, String sessionPackageName); + void setMediaButtonReceiver(in PendingIntent mbr); void setMediaButtonBroadcastReceiver(in ComponentName broadcastReceiver); void setLaunchPendingIntent(in PendingIntent pi); void destroySession(); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 20fa53d3ec32..24118b086c24 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -286,7 +286,7 @@ public void setSessionActivity(@Nullable PendingIntent pi) { @Deprecated public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { - mBinder.setMediaButtonReceiver(mbr, mContext.getPackageName()); + mBinder.setMediaButtonReceiver(mbr); } catch (RemoteException e) { Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); } diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 9a190316f4eb..c5fb5270f841 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -18,6 +18,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.List; /** @@ -102,15 +104,19 @@ public static MediaButtonReceiverHolder unflattenFromString( } /** - * Creates a new instance. + * Creates a new instance from a {@link PendingIntent}. + * + *

This method assumes the session package name has been validated and effectively belongs to + * the media session's owner. * - * @param context context * @param userId userId - * @param pendingIntent pending intent - * @return Can be {@code null} if pending intent was null. + * @param pendingIntent pending intent that will receive media button events + * @param sessionPackageName package name of media session owner + * @return {@link MediaButtonReceiverHolder} instance or {@code null} if pending intent was + * null. */ - public static MediaButtonReceiverHolder create(Context context, int userId, - PendingIntent pendingIntent, String sessionPackageName) { + public static MediaButtonReceiverHolder create( + int userId, @Nullable PendingIntent pendingIntent, String sessionPackageName) { if (pendingIntent == null) { return null; } @@ -312,7 +318,7 @@ private static int getComponentType(Context context, ComponentName componentName } private static ComponentName getComponentName(PendingIntent pendingIntent, int componentType) { - List resolveInfos = null; + List resolveInfos = Collections.emptyList(); switch (componentType) { case COMPONENT_TYPE_ACTIVITY: resolveInfos = pendingIntent.queryIntentComponents( @@ -330,32 +336,37 @@ private static ComponentName getComponentName(PendingIntent pendingIntent, int c PACKAGE_MANAGER_COMMON_FLAGS | PackageManager.GET_RECEIVERS); break; } - if (resolveInfos != null && !resolveInfos.isEmpty()) { - return createComponentName(resolveInfos.get(0)); + + for (ResolveInfo resolveInfo : resolveInfos) { + ComponentInfo componentInfo = getComponentInfo(resolveInfo); + if (componentInfo != null && TextUtils.equals(componentInfo.packageName, + pendingIntent.getCreatorPackage()) + && componentInfo.packageName != null && componentInfo.name != null) { + return new ComponentName(componentInfo.packageName, componentInfo.name); + } } + return null; } - private static ComponentName createComponentName(ResolveInfo resolveInfo) { - if (resolveInfo == null) { - return null; - } - ComponentInfo componentInfo; + /** + * Retrieves the {@link ComponentInfo} from a {@link ResolveInfo} instance. Similar to {@link + * ResolveInfo#getComponentInfo()}, but returns {@code null} if this {@link ResolveInfo} points + * to a content provider. + * + * @param resolveInfo Where to extract the {@link ComponentInfo} from. + * @return Either a non-null {@link ResolveInfo#activityInfo} or {@link + * ResolveInfo#serviceInfo}. Otherwise {@code null} if {@link ResolveInfo#providerInfo} is + * not {@code null}. + */ + private static ComponentInfo getComponentInfo(@NonNull ResolveInfo resolveInfo) { // Code borrowed from ResolveInfo#getComponentInfo(). if (resolveInfo.activityInfo != null) { - componentInfo = resolveInfo.activityInfo; + return resolveInfo.activityInfo; } else if (resolveInfo.serviceInfo != null) { - componentInfo = resolveInfo.serviceInfo; + return resolveInfo.serviceInfo; } else { - // We're not interested in content provider. - return null; - } - // Code borrowed from ComponentInfo#getComponentName(). - try { - return new ComponentName(componentInfo.packageName, componentInfo.name); - } catch (IllegalArgumentException | NullPointerException e) { - // This may be happen if resolveActivity() end up with matching multiple activities. - // see PackageManager#resolveActivity(). + // We're not interested in content providers. return null; } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 20b0c650cea8..45b10e5730ba 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -932,8 +932,7 @@ public void setFlags(int flags) throws RemoteException { } @Override - public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName) - throws RemoteException { + public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { final long token = Binder.clearCallingIdentity(); try { if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) @@ -941,7 +940,7 @@ public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName) return; } mMediaButtonReceiverHolder = - MediaButtonReceiverHolder.create(mContext, mUserId, pi, sessionPackageName); + MediaButtonReceiverHolder.create(mUserId, pi, mPackageName); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 7697c6f487ed..e2dbce251272 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -2304,9 +2304,9 @@ private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid, PendingIntent pi = mCustomMediaKeyDispatcher.getMediaButtonReceiver(keyEvent, uid, asSystemService); if (pi != null) { - mediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mContext, - mCurrentFullUserRecord.mFullUserId, pi, - /* sessionPackageName= */ ""); + mediaButtonReceiverHolder = + MediaButtonReceiverHolder.create( + mCurrentFullUserRecord.mFullUserId, pi, ""); } } } From bd980c26d4ea3305524f68d3405ba217c47ba318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Budnik?= Date: Tue, 6 Sep 2022 16:52:44 +0000 Subject: [PATCH 06/61] Enforce MediaButtonReceiver ComponentName belongs to app Adds check that enforces ComponentName's package belongs to calling app. This avoids privileged execution of arbitrary code through media button events. This is a partial revert revert of ag/19338169. Bug: 238177121 Test: atest CtsMediaBetterTogetherTestCases Change-Id: I4aba866a9758366175ea4af0d434729ad98fa48d (cherry picked from commit 1b2fa2486cc97fd9515300f858d4da2af8d8908c) Merged-In: I4aba866a9758366175ea4af0d434729ad98fa48d (cherry picked from commit 863d396f4ccabee91d51b04f72f44c34ffe351f0) (cherry picked from commit 833af484ecbe732ec086ee08a068c6010cd070c9) Merged-In: I4aba866a9758366175ea4af0d434729ad98fa48d --- .../com/android/server/media/MediaSessionRecord.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 45b10e5730ba..3a427ddeb739 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -53,6 +53,7 @@ import android.os.ResultReceiver; import android.os.SystemClock; import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import android.view.KeyEvent; @@ -951,6 +952,14 @@ public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { final long token = Binder.clearCallingIdentity(); try { + //mPackageName has been verified in MediaSessionService.enforcePackageName(). + if (receiver != null && !TextUtils.equals( + mPackageName, receiver.getPackageName())) { + EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging. + throw new IllegalArgumentException("receiver does not belong to " + + "package name provided to MediaSessionRecord. Pkg = " + mPackageName + + ", Receiver Pkg = " + receiver.getPackageName()); + } if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) != 0) { return; From 9eb8348cbc8d7cff14d3fea1156ebb4b333adf3b Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 11 Jan 2023 18:58:41 +0000 Subject: [PATCH 07/61] Revert "Ensure that only SysUI can override pending intent launch flags" This reverts commit c4d3106e347922610f8c554de3ae238175ed393e. Reason for revert: b/264884187, b/264885689 Change-Id: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba (cherry picked from commit 7bb933f48ff15d8f08d2185005b7b3e212915276) Merged-In: I9fb0d66327f3f872a92e6b9d682d58489e81e6ba --- .../com/android/server/am/PendingIntentRecord.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 33f745ef211f..dc924c11f867 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -350,16 +350,11 @@ public int sendInner(int code, Intent intent, String resolvedType, IBinder allow resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is used only by SystemUI - // to ensure that we can launch the pending intent with a consistent launch mode even - // if the provided PendingIntent is immutable (ie. to force an activity to launch into - // a new task, or to launch multiple instances if supported by the app) + // Apply any launch flags from the ActivityOptions. This is to ensure that the caller + // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - // TODO(b/254490217): Move this check into SafeActivityOptions - if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); - } + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } // Extract options before clearing calling identity From 0999c8e318873414975f8b293b831d6ad05af55c Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Thu, 4 Aug 2022 11:36:26 -0700 Subject: [PATCH 08/61] DO NOT MERGE: Context#startInstrumentation could be started from SHELL only now. Or, if an instrumentation starts another instrumentation and so on, and the original instrumentation is started from SHELL, allow all Context#startInstrumentation calls in this chain. Otherwise, it'll throw a SecurityException. Bug: 237766679 Test: atest CtsAppTestCases:InstrumentationTest Merged-In: Ia08f225c21a3933067d066a578ea4af9c23e7d4c Merged-In: I1b76f61c5fd6c9f7e738978592260945a606f40c Merged-In: I3ea7aa27bd776fec546908a37f667f680da9c892 Change-Id: I7ca7345b064e8e74f7037b8fa3ed45bb6423e406 (cherry picked from commit 5985225e777cdb96b738aeda859dff49f6c6f853) Merged-In: I7ca7345b064e8e74f7037b8fa3ed45bb6423e406 --- .../server/am/ActivityManagerService.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eaa5c5e11396..04f80434f36a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13861,6 +13861,17 @@ public boolean startInstrumentation(ComponentName className, throw new SecurityException(msg); } } + if (!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID + && callingUid != SYSTEM_UID && !hasActiveInstrumentationLocked(callingPid)) { + // If it's not debug build and not called from root/shell/system uid, reject it. + final String msg = "Permission Denial: instrumentation test " + + className + " from pid=" + callingPid + ", uid=" + callingUid + + ", pkgName=" + getPackageNameByPid(callingPid) + + " not allowed because it's not started from SHELL"; + Slog.wtfQuiet(TAG, msg); + reportStartInstrumentationFailureLocked(watcher, className, msg); + throw new SecurityException(msg); + } ActiveInstrumentation activeInstr = new ActiveInstrumentation(this); activeInstr.mClass = className; @@ -13959,6 +13970,29 @@ private void instrumentWithoutRestart(ActiveInstrumentation activeInstr, } } + @GuardedBy("this") + private boolean hasActiveInstrumentationLocked(int pid) { + if (pid == 0) { + return false; + } + synchronized (mPidsSelfLocked) { + ProcessRecord process = mPidsSelfLocked.get(pid); + return process != null && process.getActiveInstrumentation() != null; + } + } + + private String getPackageNameByPid(int pid) { + synchronized (mPidsSelfLocked) { + final ProcessRecord app = mPidsSelfLocked.get(pid); + + if (app != null && app.info != null) { + return app.info.packageName; + } + + return null; + } + } + private boolean isCallerShell() { final int callingUid = Binder.getCallingUid(); return callingUid == SHELL_UID || callingUid == ROOT_UID; From 198a39ff1e67779b5fb5b1fb9dfe7a3b3a17f2cd Mon Sep 17 00:00:00 2001 From: Hao Ke Date: Mon, 12 Dec 2022 15:49:16 +0000 Subject: [PATCH 09/61] Fix checkKeyIntentParceledCorrectly's bypass The checkKeyIntentParceledCorrectly method was added in checkKeyIntent, which was originaly only invoked when AccountManagerService deserializes the KEY_INTENT value as not NULL. However, due to the self-changing bundle technique in Parcel mismatch problems, the Intent value can change after reparceling; hence would bypass the added checkKeyIntentParceledCorrectly call. This CL did the following: - Ensure the checkKeyIntent method is also called when result.getParcelable(AccountManager.KEY_INTENT) == null. Bug: 260567867 Bug: 262230405 Test: local test, see b/262230405 Test: atest CtsAccountManagerTestCases Merged-In: I7b528f52c41767ae12731838fdd36aa26a8f3477 Change-Id: I7b528f52c41767ae12731838fdd36aa26a8f3477 (cherry picked from commit 9f623983a8d4ec48d58b0eda56fa461fc6748981) Merged-In: I7b528f52c41767ae12731838fdd36aa26a8f3477 --- .../server/accounts/AccountManagerService.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index a8672ff38e9d..8c80dfb94d53 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3513,8 +3513,7 @@ public void onResult(Bundle result) { Bundle.setDefusable(result, true); mNumResults++; Intent intent = null; - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), result)) { @@ -4873,8 +4872,10 @@ protected boolean checkKeyIntent(int authUid, Bundle bundle) { EventLog.writeEvent(0x534e4554, "250588548", authUid, ""); return false; } - Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent == null) { + return true; + } // Explicitly set an empty ClipData to ensure that we don't offer to // promote any Uris contained inside for granting purposes if (intent.getClipData() == null) { @@ -4927,7 +4928,10 @@ private boolean checkKeyIntentParceledCorrectly(Bundle bundle) { p.recycle(); Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT); - return (intent.filterEquals(simulateIntent)); + if (intent == null) { + return (simulateIntent == null); + } + return intent.filterEquals(simulateIntent); } private boolean isExportedSystemActivity(ActivityInfo activityInfo) { @@ -5072,8 +5076,7 @@ public void onResult(Bundle result) { } } } - if (result != null - && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + if (result != null) { if (!checkKeyIntent( Binder.getCallingUid(), result)) { From 7844f52b77d7fe0c90cb3b234d26f246025f28a6 Mon Sep 17 00:00:00 2001 From: Kunal Malhotra Date: Mon, 7 Nov 2022 23:33:55 +0000 Subject: [PATCH 10/61] Checking if package belongs to UID before registering broadcast receiver Test: manual testing done on device by installing test APK and checking if receiver can register Bug: 242040055 Change-Id: Ia525f218a46f8bf7fff660cec0d6432f09fdf24d Merged-In: Ia525f218a46f8bf7fff660cec0d6432f09fdf24d (cherry picked from commit 790a8d0dd329460bc60456681cb446accf2a27e0) (cherry picked from commit 8460609f01147d2a7e849eca1ca895211530b589) Merged-In: Ia525f218a46f8bf7fff660cec0d6432f09fdf24d --- services/core/java/com/android/server/am/ActiveServices.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index bad728a86c00..0007217b27dc 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3201,6 +3201,11 @@ private ServiceLookupResult retrieveServiceLocked(Intent service, throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not an isolatedProcess"); } + if (AppGlobals.getPackageManager().getPackageUid(callingPackage, + 0, userId) != callingUid) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + + "calling package not owned by calling UID "); + } // Run the service under the calling package's application. ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo( callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId); From 8ee7c4aeeb206cc91701c8d79756510c906844d5 Mon Sep 17 00:00:00 2001 From: Winson Chiu Date: Fri, 6 Jan 2023 21:26:24 +0000 Subject: [PATCH 11/61] Encode Intent scheme when serializing to URI string RESTRICT AUTOMERGE Avoids deserialization error when the scheme contains a reserved character. Bug: 261858325 Test: atest android.content.cts.IntentTest#testEncoding Merged-In: Ic34b3f796b762763db5aa7b5d7c109ae70607470 Change-Id: Ic34b3f796b762763db5aa7b5d7c109ae70607470 (cherry picked from commit bfe7e8bab48caff53dbcf2913f724de2e4f5aa81) Merged-In: Ic34b3f796b762763db5aa7b5d7c109ae70607470 --- core/java/android/content/Intent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 558082f48c7a..21df54a4910b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -11035,7 +11035,7 @@ private void toUriFragment(StringBuilder uri, String scheme, String defAction, private void toUriInner(StringBuilder uri, String scheme, String defAction, String defPackage, int flags) { if (scheme != null) { - uri.append("scheme=").append(scheme).append(';'); + uri.append("scheme=").append(Uri.encode(scheme)).append(';'); } if (mAction != null && !mAction.equals(defAction)) { uri.append("action=").append(Uri.encode(mAction)).append(';'); From 64537dd752a644dd2831049ade5522b087d1f208 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Wed, 28 Sep 2022 06:46:29 +0000 Subject: [PATCH 12/61] [RESTRICT AUTOMERGE] Strip part of the activity info of another uid if no privilege The activity info could be from another uid which is different from the app that hosts the task. The information should be trimmed if the caller app doesn't have the privilege. However, removing the entire info may result in app compatibility issues. So, only swiping the info that are sensitive to empty string. Bug: 243130512 Test: verified market app locally Test: atest RecentTasksTest Change-Id: I5b6775dd3c4e2ccdacd30741884d336b2eaa70da Merged-In: I5b6775dd3c4e2ccdacd30741884d336b2eaa70da (cherry picked from commit 5ba72200f6a66b5da48c9c3abd103a73aea1ef95) (cherry picked from commit 7be9e6efb63884f8f4bb647e537a29746bbeb9fa) Merged-In: I5b6775dd3c4e2ccdacd30741884d336b2eaa70da --- .../com/android/server/wm/AppTaskImpl.java | 2 +- .../com/android/server/wm/RecentTasks.java | 8 +++- .../com/android/server/wm/RunningTasks.java | 4 ++ .../core/java/com/android/server/wm/Task.java | 48 +++++++++++++++++++ .../android/server/wm/RecentTasksTest.java | 28 +++++++++-- 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 558939611905..3c9adfb9c4bd 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -84,7 +84,7 @@ public ActivityManager.RecentTaskInfo getTaskInfo() { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } return mService.getRecentTasks().createRecentTaskInfo(task, - false /* stripExtras */); + false /* stripExtras */, true /* getTasksAllowed */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index dca0bbda78cf..1a3d621794d3 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -974,7 +974,7 @@ private ArrayList getRecentTasksImpl(int maxNum, continue; } - res.add(createRecentTaskInfo(task, true /* stripExtras */)); + res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed)); } return res; } @@ -1890,7 +1890,8 @@ void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { /** * Creates a new RecentTaskInfo from a Task. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras, + boolean getTasksAllowed) { final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); // If the recent Task is detached, we consider it will be re-attached to the default // TaskDisplayArea because we currently only support recent overview in the default TDA. @@ -1902,6 +1903,9 @@ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; rti.lastSnapshotData.set(tr.mLastTaskSnapshotData); + if (!getTasksAllowed) { + Task.trimIneffectiveInfo(tr, rti); + } // Fill in organized child task info for the task created by organizer. if (tr.mCreatedByOrganizer) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 9864297de529..7acd5152c291 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -150,6 +150,10 @@ private RunningTaskInfo createRunningTaskInfo(Task task) { task.fillTaskInfo(rti, !mKeepIntentExtra); // Fill in some deprecated values rti.id = rti.taskId; + + if (!mAllowed) { + Task.trimIneffectiveInfo(task, rti); + } return rti; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7e9f8c388f0a..b8ef3442ffc3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3487,6 +3487,54 @@ void fillTaskInfo(TaskInfo info, boolean stripExtras, @Nullable TaskDisplayArea info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null; } + /** + * Removes the activity info if the activity belongs to a different uid, which is + * different from the app that hosts the task. + */ + static void trimIneffectiveInfo(Task task, TaskInfo info) { + final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing, + false /* traverseTopToBottom */); + final int baseActivityUid = + baseActivity != null ? baseActivity.getUid() : task.effectiveUid; + + if (info.topActivityInfo != null + && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { + // Making a copy to prevent eliminating the info in the original ActivityRecord. + info.topActivityInfo = new ActivityInfo(info.topActivityInfo); + info.topActivityInfo.applicationInfo = + new ApplicationInfo(info.topActivityInfo.applicationInfo); + + // Strip the sensitive info. + info.topActivity = new ComponentName("", ""); + info.topActivityInfo.packageName = ""; + info.topActivityInfo.taskAffinity = ""; + info.topActivityInfo.processName = ""; + info.topActivityInfo.name = ""; + info.topActivityInfo.parentActivityName = ""; + info.topActivityInfo.targetActivity = ""; + info.topActivityInfo.splitName = ""; + info.topActivityInfo.applicationInfo.className = ""; + info.topActivityInfo.applicationInfo.credentialProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.dataDir = ""; + info.topActivityInfo.applicationInfo.deviceProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.manageSpaceActivityName = ""; + info.topActivityInfo.applicationInfo.nativeLibraryDir = ""; + info.topActivityInfo.applicationInfo.nativeLibraryRootDir = ""; + info.topActivityInfo.applicationInfo.processName = ""; + info.topActivityInfo.applicationInfo.publicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanPublicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanSourceDir = ""; + info.topActivityInfo.applicationInfo.sourceDir = ""; + info.topActivityInfo.applicationInfo.taskAffinity = ""; + info.topActivityInfo.applicationInfo.name = ""; + info.topActivityInfo.applicationInfo.packageName = ""; + } + + if (task.effectiveUid != baseActivityUid) { + info.baseActivity = new ComponentName("", ""); + } + } + @Nullable PictureInPictureParams getPictureInPictureParams() { return getPictureInPictureParams(getTopMostTask()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 284728397c9f..d6a5adb2eef7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -30,6 +30,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.os.Process.NOBODY_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -1195,21 +1196,36 @@ public void testLastSnapshotData_notSet() { @Test public void testCreateRecentTaskInfo_detachedTask() { - final Task task = createTaskBuilder(".Task").setCreateActivity(true).build(); + final Task task = createTaskBuilder(".Task").build(); + final ComponentName componentName = new ComponentName("com.foo", ".BarActivity"); + new ActivityBuilder(mSupervisor.mService) + .setTask(task) + .setUid(NOBODY_UID) + .setComponent(componentName) + .build(); final TaskDisplayArea tda = task.getDisplayArea(); assertTrue(task.isAttached()); assertTrue(task.supportsMultiWindow()); - RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true); + RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + false /* getTasksAllowed */); + + assertFalse(info.topActivity.equals(componentName)); + assertFalse(info.topActivityInfo.packageName.equals(componentName.getPackageName())); + assertFalse(info.baseActivity.equals(componentName)); + // The task can be put in split screen even if it is not attached now. task.removeImmediately(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); @@ -1219,7 +1235,8 @@ public void testCreateRecentTaskInfo_detachedTask() { doReturn(false).when(tda).supportsNonResizableMultiWindow(); doReturn(false).when(task).isResizeable(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertFalse(info.supportsMultiWindow); assertFalse(info.supportsSplitScreenMultiWindow); @@ -1228,7 +1245,8 @@ public void testCreateRecentTaskInfo_detachedTask() { // the device supports it. doReturn(true).when(tda).supportsNonResizableMultiWindow(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); assertTrue(info.supportsSplitScreenMultiWindow); From 0a4ce9d204f1f4de91cd8a22f4104cba6809c28c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 18 Jan 2022 11:59:54 -0500 Subject: [PATCH 13/61] Add a limit on channel group creation Same as exists for channels This is a backport of the fix in ag/16659457, including the adjustment from ag/20920023 (changed the max value from 50000 to 6000). Test: PreferencesHelperTest Bug: 210114537 Bug: 261723753 Change-Id: Ic27efba4c54e22eebca16fc948879e652df4467b (cherry picked from commit 37b3549807d15452ac334fae316e615c3b9b8e8b & I3f3a99765c161369e1b026686a0e5f0c83ed839e) Merged-In: I3f3a99765c161369e1b026686a0e5f0c83ed839e (cherry picked from commit 38257af19e18d19075483dfa351c7e5cbb9cbf75) Merged-In: Ic27efba4c54e22eebca16fc948879e652df4467b --- .../notification/PreferencesHelper.java | 14 ++++++ .../notification/PreferencesHelperTest.java | 47 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 9d702aadbce3..d52689b0d60d 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -98,6 +98,8 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; + @VisibleForTesting + static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000; private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000; private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000; @@ -242,6 +244,7 @@ public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId) } } boolean skipWarningLogged = false; + boolean skipGroupWarningLogged = false; boolean hasSAWPermission = false; if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( @@ -294,6 +297,14 @@ public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId) String tagName = parser.getName(); // Channel groups if (TAG_GROUP.equals(tagName)) { + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + if (!skipGroupWarningLogged) { + Slog.w(TAG, "Skipping further groups for " + r.pkg + + "; app has too many"); + skipGroupWarningLogged = true; + } + continue; + } String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); @@ -807,6 +818,9 @@ public void createNotificationChannelGroup(String pkg, int uid, NotificationChan } if (fromTargetApp) { group.setBlocked(false); + if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) { + throw new IllegalStateException("Limit exceed; cannot create more groups"); + } } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); if (oldGroup != null) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 77612b9d8cef..ceacdb37d946 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -42,6 +42,7 @@ import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -3165,6 +3166,52 @@ public void testTooManyChannels_xml() throws Exception { assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, extraChannel1, true)); } + @Test + public void testTooManyGroups() { + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + try { + NotificationChannelGroup group = new NotificationChannelGroup( + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT), + String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + fail("Allowed to create too many notification channel groups"); + } catch (IllegalStateException e) { + // great + } + } + + @Test + public void testTooManyGroups_xml() throws Exception { + String extraGroup = "EXTRA"; + String extraGroup1 = "EXTRA1"; + + // create first... many... directly so we don't need a big xml blob in this test + for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) { + NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i), + String.valueOf(i)); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + } + + final String xml = "\n" + + "\n" + + "" + + "" + + "" + + ""; + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O)); + assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O)); + } + @Test public void testRestoreMultiUser() throws Exception { String pkg = "restore_pkg"; From 642c97888eceb38cd1e4fa7ac84a697a2b728260 Mon Sep 17 00:00:00 2001 From: Kate Montgomery Date: Thu, 26 Jan 2023 18:31:45 +0000 Subject: [PATCH 14/61] Fix bypass BAL via LocationManager.requestFlush Bug: 235823542 Test: atest LocationProviderManagerTest and manual tests Change-Id: I2a0fa7b99c3ad5ae839d8018ec70cb5c26e33240 (cherry picked from commit 750af79d5ccb282bb79ef40932858fbae801a48b) Merged-In: I2a0fa7b99c3ad5ae839d8018ec70cb5c26e33240 --- .../server/location/provider/LocationProviderManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 9ed63b5ce2da..eda5a94258c2 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -281,6 +281,7 @@ public void deliverOnLocationChanged(LocationResult locationResult, public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode), null, null, null, options.toBundle()); From 1c619ed83fa73e03f59877f0edf4018422f50100 Mon Sep 17 00:00:00 2001 From: Rhed Jao Date: Wed, 11 Jan 2023 16:02:27 +0800 Subject: [PATCH 15/61] [RESTRICT AUTOMERGE] Fix bypass BG-FGS and BAL via package manager APIs Opt-in for BAL of PendingIntent for following APIs: * PackageInstaller.uninstall() * PackageInstaller.installExistingPackage() * PackageInstaller.uninstallExistingPackage() * PackageInstaller.Session.commit() * PackageInstaller.Session.commitTransferred() * PackageManager.freeStorage() Bug: 230492955 Bug: 243377226 Test: atest android.security.cts.PackageInstallerTest Test: atest CtsStagedInstallHostTestCases Change-Id: I9b6f801d69ea6d2244a38dbe689e81afa4e798bf (cherry picked from commit 5f00e89989392c9ae00b360e1388d0179dfb36d7) Merged-In: I9b6f801d69ea6d2244a38dbe689e81afa4e798bf --- core/java/android/content/IntentSender.java | 41 ++++++++++++++++++- .../server/pm/PackageInstallerService.java | 11 ++++- .../server/pm/PackageInstallerSession.java | 22 ++++++++-- .../server/pm/PackageManagerService.java | 10 ++++- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index b1252fd0b21f..49d3cac63124 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -19,6 +19,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.PendingIntentInfo; +import android.app.ActivityOptions; import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.Handler; @@ -158,7 +159,7 @@ public void run() { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { - sendIntent(context, code, intent, onFinished, handler, null); + sendIntent(context, code, intent, onFinished, handler, null, null /* options */); } /** @@ -190,6 +191,42 @@ public void sendIntent(Context context, int code, Intent intent, public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler, String requiredPermission) throws SendIntentException { + sendIntent(context, code, intent, onFinished, handler, requiredPermission, + null /* options */); + } + + /** + * Perform the operation associated with this IntentSender, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * @param context The Context of the caller. This may be null if + * intent is also null. + * @param code Result code to supply back to the IntentSender's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * @param requiredPermission Name of permission that a recipient of the PendingIntent + * is required to hold. This is only valid for broadcast intents, and + * corresponds to the permission argument in + * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. + * If null, no permission is required. + * @param options Additional options the caller would like to provide to modify the sending + * behavior. May be built from an {@link ActivityOptions} to apply to an activity start. + * + * @throws SendIntentException Throws CanceledIntentException if the IntentSender + * is no longer allowing more intents to be sent through it. + * @hide + */ + public void sendIntent(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler, String requiredPermission, + @Nullable Bundle options) + throws SendIntentException { try { String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) @@ -199,7 +236,7 @@ public void sendIntent(Context context, int code, Intent intent, onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, - requiredPermission, null); + requiredPermission, options); if (res < 0) { throw new SendIntentException(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 708b2129ad29..2ca3e8f1bc1b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -25,6 +25,7 @@ import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; @@ -1243,7 +1244,10 @@ public void onUserActionRequired(Intent intent) { PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } @@ -1268,7 +1272,10 @@ public void onPackageDeleted(String basePackageName, int returnCode, String msg) PackageManager.deleteStatusToString(returnCode, msg)); fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); try { - mTarget.sendIntent(mContext, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 3ddcf17d0a47..1137fc5b5a86 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -50,6 +50,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyEventLogger; @@ -1872,7 +1873,11 @@ public void statusUpdate(Intent intent) { } } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) { try { - mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + mStatusReceiver.sendIntent(mContext, 0, intent, + null /* onFinished*/, null /* handler */, + null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignore) { } } else { // failure, let's forward and clean up this session. @@ -4375,7 +4380,10 @@ private static void sendOnUserActionRequired(Context context, IntentSender targe fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -4418,7 +4426,10 @@ private static void sendOnPackageInstalled(Context context, IntentSender target, } } try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } @@ -4443,7 +4454,10 @@ private static void sendPendingStreaming(Context context, IntentSender target, i intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready"); } try { - target.sendIntent(context, 0, intent, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, intent, null /* onFinished */, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (IntentSender.SendIntentException ignored) { } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ee2d83ea20ac..ba9eedfbf8de 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9266,7 +9266,10 @@ public void freeStorage(final String volumeUuid, final long freeStorageSize, } if (pi != null) { try { - pi.sendIntent(null, success ? 1 : 0, null, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + pi.sendIntent(null, success ? 1 : 0, null /* intent */, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, e); } @@ -16449,7 +16452,10 @@ static void onRestoreComplete(int returnCode, Context context, IntentSender targ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageManager.installStatusToPublicStatus(returnCode)); try { - target.sendIntent(context, 0, fillIn, null, null); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + target.sendIntent(context, 0, fillIn, null /* onFinished*/, + null /* handler */, null /* requiredPermission */, options.toBundle()); } catch (SendIntentException ignored) { } } From 281c6c3733d13b1fe9606c8ebd34c9aa29b6c302 Mon Sep 17 00:00:00 2001 From: Justin Dunlap Date: Wed, 1 Mar 2023 00:07:21 +0000 Subject: [PATCH 16/61] Revert "Make Activites touch opaque - DO NOT MERGE" This reverts commit 22261fa6649f6ec6441646743ad98132fcf47fe0. Reason for revert: Re-release due to functional regression Change-Id: I9ca1fa2f140d640159fabec1424c52867cf01a60 (cherry picked from commit 23bf0bda7d9b97a82ea04257318bb90677561476) Merged-In: I9ca1fa2f140d640159fabec1424c52867cf01a60 --- .../com/android/server/wm/ActivityRecord.java | 13 -- .../server/wm/ActivityRecordInputSink.java | 113 ------------------ 2 files changed, 126 deletions(-) delete mode 100644 services/core/java/com/android/server/wm/ActivityRecordInputSink.java diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b3c7510d97ed..d63d00735872 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -802,13 +802,6 @@ enum State { private AppSaturationInfo mLastAppSaturationInfo; - private final ActivityRecordInputSink mActivityRecordInputSink; - - // Activities with this uid are allowed to not create an input sink while being in the same - // task and directly above this ActivityRecord. This field is updated whenever a new activity - // is launched from this ActivityRecord. Touches are always allowed within the same uid. - int mAllowedTouchUid; - private final ColorDisplayService.ColorTransformController mColorTransformController = (matrix, translation) -> mWmService.mH.post(() -> { synchronized (mWmService.mGlobalLock) { @@ -1854,8 +1847,6 @@ private ActivityRecord(ActivityTaskManagerService _service, WindowProcessControl createTime = _createTime; } mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName); - - mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord); } /** @@ -3769,7 +3760,6 @@ void removeImmediately() { } else { onRemovedFromDisplay(); } - mActivityRecordInputSink.releaseSurfaceControl(); super.removeImmediately(); } @@ -6934,9 +6924,6 @@ void prepareSurfaces() { } else if (!show && mLastSurfaceShowing) { getSyncTransaction().hide(mSurfaceControl); } - if (show) { - mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction()); - } } if (mThumbnail != null) { mThumbnail.setShowing(getPendingTransaction(), show); diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java deleted file mode 100644 index 95b5cec9a144..000000000000 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.os.Process; -import android.view.InputWindowHandle; -import android.view.SurfaceControl; -import android.view.WindowManager; - -/** - * Creates a InputWindowHandle that catches all touches that would otherwise pass through an - * Activity. - */ -class ActivityRecordInputSink { - - private final ActivityRecord mActivityRecord; - private final String mName; - - private InputWindowHandle mInputWindowHandle; - private SurfaceControl mSurfaceControl; - - ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) { - mActivityRecord = activityRecord; - mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink " - + mActivityRecord.mActivityComponent.flattenToShortString(); - if (sourceRecord != null) { - sourceRecord.mAllowedTouchUid = mActivityRecord.getUid(); - } - } - - public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) { - boolean windowHandleChanged = updateInputWindowHandle(); - if (mSurfaceControl == null) { - mSurfaceControl = createSurface(transaction); - } - if (windowHandleChanged) { - transaction.setInputWindowInfo(mSurfaceControl, mInputWindowHandle); - } - } - - private SurfaceControl createSurface(SurfaceControl.Transaction t) { - SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null) - .setName(mName) - .setHidden(false) - .setCallsite("ActivityRecordInputSink.createSurface") - .build(); - // Put layer below all siblings (and the parent surface too) - t.setLayer(surfaceControl, Integer.MIN_VALUE); - return surfaceControl; - } - - private boolean updateInputWindowHandle() { - boolean changed = false; - if (mInputWindowHandle == null) { - mInputWindowHandle = createInputWindowHandle(); - changed = true; - } - // Don't block touches from passing through to an activity below us in the same task, if - // that activity is either from the same uid or if that activity has launched an activity - // in our uid. - final ActivityRecord activityBelowInTask = - mActivityRecord.getTask().getActivityBelow(mActivityRecord); - final boolean allowPassthrough = activityBelowInTask != null && ( - activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() - || activityBelowInTask.isUid(mActivityRecord.getUid())); - boolean notTouchable = (mInputWindowHandle.layoutParamsFlags - & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0; - if (allowPassthrough || mActivityRecord.isAppTransitioning()) { - mInputWindowHandle.layoutParamsFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - changed |= !notTouchable; - } else { - mInputWindowHandle.layoutParamsFlags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - changed |= notTouchable; - } - return changed; - } - - private InputWindowHandle createInputWindowHandle() { - InputWindowHandle inputWindowHandle = new InputWindowHandle(null, - mActivityRecord.getDisplayId()); - inputWindowHandle.replaceTouchableRegionWithCrop = true; - inputWindowHandle.name = mName; - inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; - inputWindowHandle.ownerUid = Process.myUid(); - inputWindowHandle.ownerPid = Process.myPid(); - inputWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - inputWindowHandle.inputFeatures = - WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; - return inputWindowHandle; - } - - void releaseSurfaceControl() { - if (mSurfaceControl != null) { - mSurfaceControl.release(); - mSurfaceControl = null; - } - } - -} From f1087da25e5cca21b17dfe58af306c5c36fd2d1c Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Thu, 26 Jan 2023 17:43:24 -0800 Subject: [PATCH 17/61] [RESTRICT AUTOMERGE][pm] prevent system app downgrades of versions lower than preload Also remove misleading commandline output. BUG: 256202273 Test: manual 1. Install preload system app v90, reboot 2. (W/O data, W/ Flag, 90->80 NOK) adb install -d ~/Downloads/PrivApplication_80.apk Performing Streamed Install adb: failed to install /usr/local/google/home/schfan/Downloads/PrivApplication_80.apk: Failure [INSTALL_FAILED_VERSION_DOWNGRADE: System app: com.example.privapplication cannot be downgraded to older than its preloaded version on the system image. Update version code 80 is older than current 90] 3. (90->100) Install data app v100 4. (W/ data, W/O Flag, 100->90 NOK) adb install ~/Downloads/PrivApplication_90.apk Performing Streamed Install adb: failed to install /usr/local/google/home/schfan/Downloads/PrivApplication_90.apk: Failure [INSTALL_FAILED_VERSION_DOWNGRADE: Downgrade detected: Update version code 90 is older than current 100] 5. (W/ data, W/ Flag, 100->90 downgrade OK) adb install -d ~/Downloads/PrivApplication_90.apk Performing Streamed Install Success 6. (90->100) Install v100 6. (W/data, W/ Flag, 100->80 NOK) adb install -d ~/Downloads/PrivApplication_80.apk Performing Streamed Install adb: failed to install /usr/local/google/home/schfan/Downloads/PrivApplication_80.apk: Failure [INSTALL_FAILED_VERSION_DOWNGRADE: System app: com.example.privapplication cannot be downgraded to older than its preloaded version on the system image. Update version code 80 is older than current 90] Change-Id: I5a8ee9e29a3a58f6e3fd188e0122355744b8b0ce (cherry picked from commit a4484d7f1be1fa413258fe18644d61f85611f586) (cherry picked from commit on googleplex-android-review.googlesource.com host: cc9d3867082ac1518b7264c3752442f5ca112aa1) Merged-In: I5a8ee9e29a3a58f6e3fd188e0122355744b8b0ce --- .../server/pm/PackageManagerService.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ba9eedfbf8de..b7a2a4c2ac3a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -26943,10 +26943,10 @@ private int verifyReplacingVersionCode(PackageInfoLite pkgLite, // will be null whereas dataOwnerPkg will contain information about the package // which was uninstalled while keeping its data. AndroidPackage dataOwnerPkg = mPackages.get(packageName); + PackageSetting dataOwnerPs = mSettings.getPackageLPr(packageName); if (dataOwnerPkg == null) { - PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null) { - dataOwnerPkg = ps.pkg; + if (dataOwnerPs != null) { + dataOwnerPkg = dataOwnerPs.getPkg(); } } @@ -26970,12 +26970,30 @@ private int verifyReplacingVersionCode(PackageInfoLite pkgLite, if (dataOwnerPkg != null) { if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags, dataOwnerPkg.isDebuggable())) { + // Downgrade is not permitted; a lower version of the app will not be allowed try { checkDowngrade(dataOwnerPkg, pkgLite); } catch (PackageManagerException e) { Slog.w(TAG, "Downgrade detected: " + e.getMessage()); return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; } + } else if (dataOwnerPs.isSystem()) { + // Downgrade is permitted, but system apps can't be downgraded below + // the version preloaded onto the system image + final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr( + dataOwnerPs); + if (disabledPs != null) { + dataOwnerPkg = disabledPs.getPkg(); + } + try { + checkDowngrade(dataOwnerPkg, pkgLite); + } catch (PackageManagerException e) { + String errorMsg = "System app: " + packageName + " cannot be downgraded to" + + " older than its preloaded version on the system image. " + + e.getMessage(); + Slog.w(TAG, errorMsg); + return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; + } } } } From ebc0bad02d51886a72fcd77705992ac83777bb20 Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Thu, 2 Feb 2023 10:35:56 -0800 Subject: [PATCH 18/61] [RESTRICT AUTOMERGE][pm] still allow debuggable for system app downgrades Turns out we do have internal tests that downgrades system apps, so adding this exception to allow for that. BUG: 267232653 BUG: 256202273 Test: manual Change-Id: Ie281bbdc8788ee64ff99a7c5150da7ce7926235e (cherry picked from commit ceeca68b8c3f0ed8427b0212f63defe2f075146e) (cherry picked from commit on googleplex-android-review.googlesource.com host: 636cdf22b90ccb4866f380c307b7e1b92da03ed9) Merged-In: Ie281bbdc8788ee64ff99a7c5150da7ce7926235e --- .../server/pm/PackageManagerService.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b7a2a4c2ac3a..82eee32ddddc 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -26985,14 +26985,18 @@ private int verifyReplacingVersionCode(PackageInfoLite pkgLite, if (disabledPs != null) { dataOwnerPkg = disabledPs.getPkg(); } - try { - checkDowngrade(dataOwnerPkg, pkgLite); - } catch (PackageManagerException e) { - String errorMsg = "System app: " + packageName + " cannot be downgraded to" - + " older than its preloaded version on the system image. " - + e.getMessage(); - Slog.w(TAG, errorMsg); - return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; + if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) { + // Only restrict non-debuggable builds and non-debuggable version of the app + try { + checkDowngrade(dataOwnerPkg, pkgLite); + } catch (PackageManagerException e) { + String errorMsg = "System app: " + packageName + + " cannot be downgraded to" + + " older than its preloaded version on the system image. " + + e.getMessage(); + Slog.w(TAG, errorMsg); + return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; + } } } } From 1b168c575748c866a5caf9276d2cbf8f22d0bf24 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Thu, 9 Feb 2023 12:28:26 -0800 Subject: [PATCH 19/61] Checks if AccessibilityServiceInfo is within parcelable size. - If too large when parsing service XMLs then skip this service. - If too large when a service attempts to update its own info then throw an error. Bug: 261589597 Test: atest AccessibilityServiceInfoTest Change-Id: Iffc0cd48cc713f7904d68059e141cb7de5a4b906 Merged-In: Iffc0cd48cc713f7904d68059e141cb7de5a4b906 (cherry picked from commit on googleplex-android-review.googlesource.com host: 553232c29079fbeab28f95307d025c1426aa7142) Merged-In: Iffc0cd48cc713f7904d68059e141cb7de5a4b906 --- .../accessibilityservice/AccessibilityService.java | 4 ++++ .../accessibilityservice/AccessibilityServiceInfo.java | 10 ++++++++++ .../accessibility/AccessibilityManagerService.java | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 7e382870b016..1d72a1cac92c 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -2054,6 +2054,10 @@ private void sendServiceInfo() { IAccessibilityServiceConnection connection = AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId); if (mInfo != null && connection != null) { + if (!mInfo.isWithinParcelableSize()) { + throw new IllegalStateException( + "Cannot update service info: size is larger than safe parcelable limits."); + } try { connection.setServiceInfo(mInfo); mInfo = null; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 04c784ea1c17..bd4e1f88d6cd 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -40,6 +40,7 @@ import android.graphics.drawable.Drawable; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -1063,6 +1064,15 @@ public int describeContents() { return 0; } + /** @hide */ + public final boolean isWithinParcelableSize() { + final Parcel parcel = Parcel.obtain(); + writeToParcel(parcel, 0); + final boolean result = parcel.dataSize() <= IBinder.MAX_IPC_SIZE; + parcel.recycle(); + return result; + } + public void writeToParcel(Parcel parcel, int flagz) { parcel.writeInt(eventTypes); parcel.writeStringArray(packageNames); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 3659eedc401d..05dcea265763 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1610,6 +1610,12 @@ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState u AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); + if (!accessibilityServiceInfo.isWithinParcelableSize()) { + Slog.e(LOG_TAG, "Skipping service " + + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + + " because service info size is larger than safe parcelable limits."); + continue; + } if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { // Restore the crashed attribute. accessibilityServiceInfo.crashed = true; From 0ea18d4a8b26fcd0919a05a4f8f60392946bffd9 Mon Sep 17 00:00:00 2001 From: Orion Hodson Date: Thu, 7 Apr 2022 21:42:04 +0100 Subject: [PATCH 20/61] Uri: check authority and scheme as part of determining URI path The interpretation of the path depends on whether the scheme or authority are specified and should be observed when unparcelling URIs. Bug: 171966843 Test: atest FrameworksCoreTests:android.net.UriTest Test: atest com.android.devicehealthchecks.SystemAppCheck Change-Id: I06981d1c6e387b16df792494523994518848db37 (cherry picked from commit f37a94ae920fa5879c557603fc285942ec4b84b1) (cherry picked from commit on googleplex-android-review.googlesource.com host: d83281c73070f2428754912ede95ecb0e3d69cd5) Merged-In: I06981d1c6e387b16df792494523994518848db37 --- core/java/android/net/Uri.java | 22 +++++--- .../coretests/src/android/net/UriTest.java | 54 +++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 815e4f0c9071..d71faee4cc8d 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1205,13 +1205,16 @@ private HierarchicalUri(String scheme, Part authority, PathPart path, } static Uri readFrom(Parcel parcel) { - return new HierarchicalUri( - parcel.readString8(), - Part.readFrom(parcel), - PathPart.readFrom(parcel), - Part.readFrom(parcel), - Part.readFrom(parcel) - ); + final String scheme = parcel.readString8(); + final Part authority = Part.readFrom(parcel); + // In RFC3986 the path should be determined based on whether there is a scheme or + // authority present (https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3). + final boolean hasSchemeOrAuthority = + (scheme != null && scheme.length() > 0) || !authority.isEmpty(); + final PathPart path = PathPart.readFrom(hasSchemeOrAuthority, parcel); + final Part query = Part.readFrom(parcel); + final Part fragment = Part.readFrom(parcel); + return new HierarchicalUri(scheme, authority, path, query, fragment); } public int describeContents() { @@ -2270,6 +2273,11 @@ static PathPart readFrom(Parcel parcel) { } } + static PathPart readFrom(boolean hasSchemeOrAuthority, Parcel parcel) { + final PathPart path = readFrom(parcel); + return hasSchemeOrAuthority ? makeAbsolute(path) : path; + } + /** * Creates a path from the encoded string. * diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index e083b0d460a2..3733bfa586d1 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -48,6 +48,7 @@ public void testToStringWithPathOnly() { public void testParcelling() { parcelAndUnparcel(Uri.parse("foo:bob%20lee")); parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment")); + parcelAndUnparcel(Uri.fromParts("https", "www.google.com", null)); parcelAndUnparcel(new Uri.Builder() .scheme("http") .authority("crazybob.org") @@ -890,9 +891,62 @@ private static void assertUnparcelLegacyPart_fails(Class partClass) throws Excep Throwable targetException = expected.getTargetException(); // Check that the exception was thrown for the correct reason. assertEquals("Unknown representation: 0", targetException.getMessage()); + } finally { + parcel.recycle(); } } + private Uri buildUriFromRawParcel(boolean argumentsEncoded, + String scheme, + String authority, + String path, + String query, + String fragment) { + // Representation value (from AbstractPart.REPRESENTATION_{ENCODED,DECODED}). + final int representation = argumentsEncoded ? 1 : 2; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeInt(3); // hierarchical + parcel.writeString8(scheme); + parcel.writeInt(representation); + parcel.writeString8(authority); + parcel.writeInt(representation); + parcel.writeString8(path); + parcel.writeInt(representation); + parcel.writeString8(query); + parcel.writeInt(representation); + parcel.writeString8(fragment); + parcel.setDataPosition(0); + return Uri.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + + public void testUnparcelMalformedPath() { + // Regression tests for b/171966843. + + // Test cases with arguments encoded (covering testing `scheme` * `authority` options). + Uri uri0 = buildUriFromRawParcel(true, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/@evil.com", uri0.toString()); + Uri uri1 = buildUriFromRawParcel(true, null, "google.com", "@evil.com", "name=spark", "x"); + assertEquals("//google.com/@evil.com?name=spark#x", uri1.toString()); + Uri uri2 = buildUriFromRawParcel(true, "http:", null, "@evil.com", null, null); + assertEquals("http::/@evil.com", uri2.toString()); + Uri uri3 = buildUriFromRawParcel(true, null, null, "@evil.com", null, null); + assertEquals("@evil.com", uri3.toString()); + + // Test cases with arguments not encoded (covering testing `scheme` * `authority` options). + Uri uriA = buildUriFromRawParcel(false, "https", "google.com", "@evil.com", null, null); + assertEquals("https://google.com/%40evil.com", uriA.toString()); + Uri uriB = buildUriFromRawParcel(false, null, "google.com", "@evil.com", null, null); + assertEquals("//google.com/%40evil.com", uriB.toString()); + Uri uriC = buildUriFromRawParcel(false, "http:", null, "@evil.com", null, null); + assertEquals("http::/%40evil.com", uriC.toString()); + Uri uriD = buildUriFromRawParcel(false, null, null, "@evil.com", "name=spark", "y"); + assertEquals("%40evil.com?name%3Dspark#y", uriD.toString()); + } + public void testToSafeString() { checkToSafeString("tel:xxxxxx", "tel:Google"); checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); From e058a1b17007d73b9b65070a82a959d5bffe9444 Mon Sep 17 00:00:00 2001 From: Thomas Stuart Date: Mon, 21 Nov 2022 17:38:21 -0800 Subject: [PATCH 21/61] enforce stricter rules when registering phoneAccounts - include disable accounts when looking up accounts for a package to check if the limit is reached (10) - put a new limit of 10 supported schemes - put a new limit of 256 characters per scheme - put a new limit of 256 characters per address - ensure the Icon can write to memory w/o throwing an exception bug: 259064622 bug: 256819769 Test: cts + unit Change-Id: Ia7d8d00d9de0fb6694ded6a80c40bd55d7fdf7a7 Merged-In: Ia7d8d00d9de0fb6694ded6a80c40bd55d7fdf7a7 (cherry picked from commit on googleplex-android-review.googlesource.com host: 6a02885f90fa64d88bac31efbcdbc2bfe0a9328f) Merged-In: Ia7d8d00d9de0fb6694ded6a80c40bd55d7fdf7a7 --- .../java/android/telecom/PhoneAccount.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index e332d3ff2b4d..808d032f5d66 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -517,6 +517,11 @@ public Builder setLabel(CharSequence label) { /** * Sets the address. See {@link PhoneAccount#getAddress}. + *

+ * Note: The entire URI value is limited to 256 characters. This check is + * enforced when registering the PhoneAccount via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an + * {@link IllegalArgumentException} to be thrown if URI is over 256. * * @param value The address of the phone account. * @return The builder. @@ -550,6 +555,10 @@ public Builder setCapabilities(int value) { /** * Sets the icon. See {@link PhoneAccount#getIcon}. + *

+ * Note: An {@link IllegalArgumentException} if the Icon cannot be written to memory. + * This check is enforced when registering the PhoneAccount via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} * * @param icon The icon to set. */ @@ -583,6 +592,10 @@ public Builder setShortDescription(CharSequence value) { /** * Specifies an additional URI scheme supported by the {@link PhoneAccount}. * + *

+ * Each URI scheme is limited to 256 characters. Adding a scheme over 256 characters will + * cause an {@link IllegalArgumentException} to be thrown when the account is registered. + * * @param uriScheme The URI scheme. * @return The builder. */ @@ -596,6 +609,12 @@ public Builder addSupportedUriScheme(String uriScheme) { /** * Specifies the URI schemes supported by the {@link PhoneAccount}. * + *

+ * A max of 10 URI schemes can be added per account. Additionally, each URI scheme is + * limited to 256 characters. Adding more than 10 URI schemes or 256 characters on any + * scheme will cause an {@link IllegalArgumentException} to be thrown when the account + * is registered. + * * @param uriSchemes The URI schemes. * @return The builder. */ From 767f4e67efd3cb9d36d05bc4799ea7256cd95374 Mon Sep 17 00:00:00 2001 From: Linus Tufvesson Date: Wed, 1 Mar 2023 11:03:01 +0100 Subject: [PATCH 22/61] Make Activites touch opaque - DO NOT MERGE Block touches from passing through activities by adding a dedicated surface that consumes all touches that would otherwise pass through the bounds availble to the Activity. + Keep displayId in sync for ActivityRecord Bug: 194480991 Test: atest CtsWindowManagerDeviceTestCases:ActivityRecordInputSinkTests Test: atest CtsWindowManagerDeviceTestCases:CrossAppDragAndDropTests Test: atest CtsWindowManagerDeviceTestCases:PinnedStackTests Test: Used "System > Developer Options > Simulate secondary display" to test that moving activites between displays work as intended. Change-Id: Ie74674c87c81c571089463349ac6233717ed9f33 (cherry picked from commit on googleplex-android-review.googlesource.com host: a418847bb8de788905aced4f59437de7cbfc5360) Merged-In: Ie74674c87c81c571089463349ac6233717ed9f33 --- .../com/android/server/wm/ActivityRecord.java | 13 ++ .../server/wm/ActivityRecordInputSink.java | 117 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 services/core/java/com/android/server/wm/ActivityRecordInputSink.java diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d63d00735872..b3c7510d97ed 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -802,6 +802,13 @@ enum State { private AppSaturationInfo mLastAppSaturationInfo; + private final ActivityRecordInputSink mActivityRecordInputSink; + + // Activities with this uid are allowed to not create an input sink while being in the same + // task and directly above this ActivityRecord. This field is updated whenever a new activity + // is launched from this ActivityRecord. Touches are always allowed within the same uid. + int mAllowedTouchUid; + private final ColorDisplayService.ColorTransformController mColorTransformController = (matrix, translation) -> mWmService.mH.post(() -> { synchronized (mWmService.mGlobalLock) { @@ -1847,6 +1854,8 @@ private ActivityRecord(ActivityTaskManagerService _service, WindowProcessControl createTime = _createTime; } mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName); + + mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord); } /** @@ -3760,6 +3769,7 @@ void removeImmediately() { } else { onRemovedFromDisplay(); } + mActivityRecordInputSink.releaseSurfaceControl(); super.removeImmediately(); } @@ -6924,6 +6934,9 @@ void prepareSurfaces() { } else if (!show && mLastSurfaceShowing) { getSyncTransaction().hide(mSurfaceControl); } + if (show) { + mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction()); + } } if (mThumbnail != null) { mThumbnail.setShowing(getPendingTransaction(), show); diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java new file mode 100644 index 000000000000..95a6e8b8b88f --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.Process; +import android.view.InputWindowHandle; +import android.view.SurfaceControl; +import android.view.WindowManager; + +/** + * Creates a InputWindowHandle that catches all touches that would otherwise pass through an + * Activity. + */ +class ActivityRecordInputSink { + + private final ActivityRecord mActivityRecord; + private final String mName; + + private InputWindowHandle mInputWindowHandle; + private SurfaceControl mSurfaceControl; + + ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) { + mActivityRecord = activityRecord; + mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink " + + mActivityRecord.mActivityComponent.flattenToShortString(); + if (sourceRecord != null) { + sourceRecord.mAllowedTouchUid = mActivityRecord.getUid(); + } + } + + public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) { + boolean windowHandleChanged = updateInputWindowHandle(); + if (mSurfaceControl == null) { + mSurfaceControl = createSurface(transaction); + } + if (windowHandleChanged) { + transaction.setInputWindowInfo(mSurfaceControl, mInputWindowHandle); + } + } + + private SurfaceControl createSurface(SurfaceControl.Transaction t) { + SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null) + .setName(mName) + .setHidden(false) + .setCallsite("ActivityRecordInputSink.createSurface") + .build(); + // Put layer below all siblings (and the parent surface too) + t.setLayer(surfaceControl, Integer.MIN_VALUE); + return surfaceControl; + } + + private boolean updateInputWindowHandle() { + boolean changed = false; + if (mInputWindowHandle == null) { + mInputWindowHandle = createInputWindowHandle(); + changed = true; + } + // Don't block touches from passing through to an activity below us in the same task, if + // that activity is either from the same uid or if that activity has launched an activity + // in our uid. + final ActivityRecord activityBelowInTask = + mActivityRecord.getTask().getActivityBelow(mActivityRecord); + final boolean allowPassthrough = activityBelowInTask != null && ( + activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid() + || activityBelowInTask.isUid(mActivityRecord.getUid())); + boolean notTouchable = (mInputWindowHandle.layoutParamsFlags + & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0; + if (allowPassthrough || mActivityRecord.isAppTransitioning()) { + mInputWindowHandle.layoutParamsFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + changed |= !notTouchable; + } else { + mInputWindowHandle.layoutParamsFlags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + changed |= notTouchable; + } + if (mInputWindowHandle.displayId != mActivityRecord.getDisplayId()) { + mInputWindowHandle.displayId = mActivityRecord.getDisplayId(); + changed = true; + } + return changed; + } + + private InputWindowHandle createInputWindowHandle() { + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, + mActivityRecord.getDisplayId()); + inputWindowHandle.replaceTouchableRegionWithCrop = true; + inputWindowHandle.name = mName; + inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; + inputWindowHandle.ownerUid = Process.myUid(); + inputWindowHandle.ownerPid = Process.myPid(); + inputWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + inputWindowHandle.inputFeatures = + WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + return inputWindowHandle; + } + + void releaseSurfaceControl() { + if (mSurfaceControl != null) { + mSurfaceControl.release(); + mSurfaceControl = null; + } + } + +} From 4f9f35f32a1e49d32ba0f277d05f216975585804 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 5 Jan 2023 22:45:21 +0000 Subject: [PATCH 23/61] Trim strings added to persistent snoozed notification storage. This is a backport of ag/20581190 and includes the fix in ag/20778075. Note that on this branch, clearData doesn't seem to actually clear persistent storage. Bug: 258422365 Test: atest NotificationManagerServiceTest SnoozeHelperTest Change-Id: If7c7db6694330ffbac551d044efadb26219fe17f Merged-In: I5a2823f10053ea8c83c612a567d6d4f1b6af23e7 Merged-In: Ie809cb4d648a40622618e0fb374f36b6d8dc972a (cherry picked from commit on googleplex-android-review.googlesource.com host: b8a07871459ed895fc814730e198df4a0b5860dc) Merged-In: If7c7db6694330ffbac551d044efadb26219fe17f --- .../server/notification/SnoozeHelper.java | 29 +++++++++---- .../server/notification/SnoozeHelperTest.java | 41 ++++++++++++++++++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 2e08a9cbf844..f3b92eaf6473 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -64,6 +64,9 @@ public class SnoozeHelper { static final int CONCURRENT_SNOOZE_LIMIT = 500; + // A safe size for strings to be put in persistent storage, to avoid breaking the XML write. + static final int MAX_STRING_LENGTH = 1000; + protected static final String XML_TAG_NAME = "snoozed-notifications"; private static final String XML_SNOOZED_NOTIFICATION = "notification"; @@ -152,7 +155,7 @@ protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, Stri ArrayMap snoozed = mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg)); if (snoozed != null) { - time = snoozed.get(key); + time = snoozed.get(getTrimmedString(key)); } } if (time == null) { @@ -166,7 +169,7 @@ protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, ArrayMap snoozed = mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg)); if (snoozed != null) { - return snoozed.get(key); + return snoozed.get(getTrimmedString(key)); } } return null; @@ -251,7 +254,8 @@ protected void snooze(NotificationRecord record, long duration) { scheduleRepost(pkg, key, userId, duration); Long activateAt = System.currentTimeMillis() + duration; synchronized (mLock) { - storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); + storeRecordLocked(pkg, getTrimmedString(key), userId, mPersistedSnoozedNotifications, + activateAt); } } @@ -262,8 +266,10 @@ protected void snooze(NotificationRecord record, String contextId) { int userId = record.getUser().getIdentifier(); if (contextId != null) { synchronized (mLock) { - storeRecordLocked(record.getSbn().getPackageName(), record.getKey(), - userId, mPersistedSnoozedNotificationsWithContext, contextId); + storeRecordLocked(record.getSbn().getPackageName(), + getTrimmedString(record.getKey()), + userId, mPersistedSnoozedNotificationsWithContext, + getTrimmedString(contextId)); } } snooze(record); @@ -280,6 +286,13 @@ private void snooze(NotificationRecord record) { } } + private String getTrimmedString(String key) { + if (key != null && key.length() > MAX_STRING_LENGTH) { + return key.substring(0, MAX_STRING_LENGTH); + } + return key; + } + private void storeRecordLocked(String pkg, String key, Integer userId, ArrayMap> targets, T object) { @@ -384,12 +397,14 @@ protected void repost(String key, boolean muteOnReturn) { } protected void repost(String key, int userId, boolean muteOnReturn) { + final String trimmedKey = getTrimmedString(key); + NotificationRecord record; synchronized (mLock) { final String pkg = mPackages.remove(key); mUsers.remove(key); - removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications); - removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); + removeRecordLocked(pkg, trimmedKey, userId, mPersistedSnoozedNotifications); + removeRecordLocked(pkg, trimmedKey, userId, mPersistedSnoozedNotificationsWithContext); ArrayMap records = mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 8bead5774548..883613f6a2b6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -69,6 +69,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) @@ -247,6 +248,37 @@ public void testScheduleRepostsForPersistedNotifications() throws Exception { assertEquals("key2", captor2.getValue().getIntent().getStringExtra(EXTRA_KEY)); } + @Test + public void testLongTagPersistedNotification() throws Exception { + String longTag = String.join("", Collections.nCopies(66000, "A")); + NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, 0); + + // We store the full key in temp storage. + ArgumentCaptor captor = ArgumentCaptor.forClass(PendingIntent.class); + verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture()); + assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length()); + + TypedXmlSerializer serializer = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mSnoozeHelper.writeXml(serializer); + serializer.endDocument(); + serializer.flush(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser, 4); + + mSnoozeHelper.scheduleRepostsForPersistedNotifications(5); + + // We trim the key in persistent storage. + verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture()); + assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length()); + } + @Test public void testSnoozeForTime() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); @@ -595,13 +627,20 @@ public void repostGroupSummary_repostsSummary() throws Exception { public void testClearData() { // snooze 2 from same package NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); - NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); + NotificationRecord r2 = getNotificationRecord("pkg", 2, + "two" + String.join("", Collections.nCopies(66000, "2")), UserHandle.SYSTEM); mSnoozeHelper.snooze(r, 1000); mSnoozeHelper.snooze(r2, 1000); assertTrue(mSnoozeHelper.isSnoozed( UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey())); assertTrue(mSnoozeHelper.isSnoozed( UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey())); + assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + r.getUser().getIdentifier(), r.getSbn().getPackageName(), + r.getSbn().getKey())); + assertFalse(0L == mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + r2.getUser().getIdentifier(), r2.getSbn().getPackageName(), + r2.getSbn().getKey())); // clear data mSnoozeHelper.clearData(UserHandle.USER_SYSTEM, "pkg"); From 252c585bea07fa61e87c350e4dd733f33945c3e3 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Wed, 15 Feb 2023 15:20:25 -0800 Subject: [PATCH 24/61] Limit the number of shortcuts per app that can be retained by system This is a second attempt at fixing the issue, the previous CL ag/20642213 was reverted because it simply throws an exception when the limit is reached, which causes apps to crash since chat apps tends to be sending large amount of conversation shortcuts and they have no way to know how many of these shortcuts are still cached by the system. Instead of throwing an exception, this CL simply removes excessive shortcuts to avoid crashes. Currently there is a limit on the number of shortcuts an app can publish in respect to each launcher activity. This CL further implements a global maximum of total number of shortcuts that can be retained for an app to mitigate from any potential system health issue. When the global maximum is reached, ShortcutService will proactively removes shortcuts from system memory. Cached shortcuts are removed first, followed by dynamic shortcuts, using last updated time as tie-breaker. This CL additionally addresses an unexpected flow where re-publishing previously removed shortcuts that are still retained by the system could cause the total number of shortcuts to exceed previously set limit. Bug: 250576066 233155034 Test: manual Change-Id: I001c7a87b62aefa9487bf8efaf3cd02d7cb21521 Merged-In: I001c7a87b62aefa9487bf8efaf3cd02d7cb21521 (cherry picked from commit on googleplex-android-review.googlesource.com host: 94437e989c0391b2dbf28d33120fdc28a4ce8d4d) Merged-In: I001c7a87b62aefa9487bf8efaf3cd02d7cb21521 --- .../android/server/pm/ShortcutPackage.java | 88 ++++++++++++++++++- .../android/server/pm/ShortcutService.java | 25 +++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index c55a672aafb7..d9c88dca212d 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -405,6 +405,7 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, @NonNull List changedShortcuts) { Preconditions.checkArgument(newShortcut.isEnabled(), "pushDynamicShortcuts() cannot publish disabled shortcuts"); + ensureShortcutCountBeforePush(); newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); @@ -412,7 +413,7 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId()); boolean deleted = false; - if (oldShortcut == null) { + if (oldShortcut == null || !oldShortcut.isDynamic()) { final ShortcutService service = mShortcutUser.mService; final int maxShortcuts = service.getMaxActivityShortcuts(); @@ -422,7 +423,6 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) { // Max has reached. Delete the shortcut with lowest rank. - // Sort by isManifestShortcut() and getRank(). Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator); @@ -437,7 +437,8 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, changedShortcuts.add(shortcut); deleted = deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true) != null; } - } else { + } + if (oldShortcut != null) { // It's an update case. // Make sure the target is updatable. (i.e. should be mutable.) oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false); @@ -463,6 +464,32 @@ public boolean pushDynamicShortcut(@NonNull ShortcutInfo newShortcut, return deleted; } + private void ensureShortcutCountBeforePush() { + final ShortcutService service = mShortcutUser.mService; + // Ensure the total number of shortcuts doesn't exceed the hard limit per app. + final int maxShortcutPerApp = service.getMaxAppShortcuts(); + synchronized (mLock) { + final List appShortcuts = mShortcuts.values().stream().filter(si -> + !si.isPinned()).collect(Collectors.toList()); + if (appShortcuts.size() >= maxShortcutPerApp) { + // Max has reached. Removes shortcuts until they fall within the hard cap. + // Sort by isManifestShortcut(), isDynamic() and getLastChangedTimestamp(). + Collections.sort(appShortcuts, mShortcutTypeRankAndTimeComparator); + + while (appShortcuts.size() >= maxShortcutPerApp) { + final ShortcutInfo shortcut = appShortcuts.remove(appShortcuts.size() - 1); + if (shortcut.isDeclaredInManifest()) { + // All shortcuts are manifest shortcuts and cannot be removed. + throw new IllegalArgumentException(getPackageName() + " has published " + + appShortcuts.size() + " manifest shortcuts across different" + + " activities."); + } + forceDeleteShortcutInner(shortcut.getId()); + } + } + } + } + /** * Remove all shortcuts that aren't pinned, cached nor dynamic. * @@ -1368,6 +1395,61 @@ private boolean pushOutExcessShortcuts() { return Integer.compare(a.getRank(), b.getRank()); }; + /** + * To sort by isManifestShortcut(), isDynamic(), getRank() and + * getLastChangedTimestamp(). i.e. manifest shortcuts come before non-manifest shortcuts, + * dynamic shortcuts come before floating shortcuts, then sort by last changed timestamp. + * + * This is used to decide which shortcuts to remove when the total number of shortcuts retained + * for the app exceeds the limit defined in {@link ShortcutService#getMaxAppShortcuts()}. + * + * (Note the number of manifest shortcuts is always <= the max number, because if there are + * more, ShortcutParser would ignore the rest.) + */ + final Comparator mShortcutTypeRankAndTimeComparator = (ShortcutInfo a, + ShortcutInfo b) -> { + if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) { + return -1; + } + if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) { + return 1; + } + if (a.isDynamic() && b.isDynamic()) { + return Integer.compare(a.getRank(), b.getRank()); + } + if (a.isDynamic()) { + return -1; + } + if (b.isDynamic()) { + return 1; + } + if (a.isCached() && b.isCached()) { + // if both shortcuts are cached, prioritize shortcuts cached by people tile, + if (a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE) + && !b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) { + return -1; + } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE) + && b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) { + return 1; + } + // followed by bubbles. + if (a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES) + && !b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) { + return -1; + } else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES) + && b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) { + return 1; + } + } + if (a.isCached()) { + return -1; + } + if (b.isCached()) { + return 1; + } + return Long.compare(b.getLastChangedTimestamp(), a.getLastChangedTimestamp()); + }; + /** * Build a list of shortcuts for each target activity and return as a map. The result won't * contain "floating" shortcuts because they don't belong on any activities. diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 4e9e7a026a90..b2c238b1fe83 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -179,6 +179,9 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; + @VisibleForTesting + static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100; + @VisibleForTesting static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; @@ -253,6 +256,11 @@ interface ConfigConstants { */ String KEY_MAX_SHORTCUTS = "max_shortcuts"; + /** + * Key name for the max shortcuts can be retained in system ram per app. (int) + */ + String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app"; + /** * Key name for icon compression quality, 0-100. */ @@ -325,10 +333,15 @@ public boolean test(PackageInfo pi) { new SparseArray<>(); /** - * Max number of dynamic + manifest shortcuts that each application can have at a time. + * Max number of dynamic + manifest shortcuts that each activity can have at a time. */ private int mMaxShortcuts; + /** + * Max number of shortcuts that can exists in system ram for each application. + */ + private int mMaxShortcutsPerApp; + /** * Max number of updating API calls that each application can make during the interval. */ @@ -790,6 +803,9 @@ boolean updateConfigurationLocked(String config) { mMaxShortcuts = Math.max(0, (int) parser.getLong( ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY)); + mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP)); + final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() ? (int) parser.getLong( ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, @@ -1757,6 +1773,13 @@ int getMaxActivityShortcuts() { return mMaxShortcuts; } + /** + * Return the max number of shortcuts can be retaiend in system ram for each application. + */ + int getMaxAppShortcuts() { + return mMaxShortcutsPerApp; + } + /** * - Sends a notification to LauncherApps * - Write to file From 4a7948f83550ec0bd0636fb718ed274ba725acac Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 13 Feb 2023 14:56:14 +0800 Subject: [PATCH 25/61] Re-enforce MANAGE_ACTIVITY_TASKS for applySyncTransaction The conditional permission was introduced for TaskFragmentOrganizer, but not really needed. Remove the conditional check. Bug: 259938771 Test: pass existing tests Merged-In: I666b9ee6b6076766513b97e675fdbaa002428601 Change-Id: I666b9ee6b6076766513b97e675fdbaa002428601 (cherry picked from commit on googleplex-android-review.googlesource.com host: 6d848929eab6249b0ba1b8bd6d454744850b1718) Merged-In: I666b9ee6b6076766513b97e675fdbaa002428601 --- core/api/test-current.txt | 2 +- .../java/android/window/TaskFragmentOrganizer.java | 9 --------- core/java/android/window/WindowOrganizer.java | 7 ++----- .../server/wm/WindowOrganizerController.java | 2 +- .../wm/TaskFragmentOrganizerControllerTest.java | 14 -------------- 5 files changed, 4 insertions(+), 30 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a80468b6d980..14a6b1a25bcc 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3324,7 +3324,7 @@ package android.window { public class WindowOrganizer { ctor public WindowOrganizer(); - method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 9c2fde04e4d2..7711ebe525b3 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -157,15 +157,6 @@ public void applyTransaction(@NonNull WindowContainerTransaction t) { super.applyTransaction(t); } - // Suppress the lint because it is not a registration method. - @SuppressWarnings("ExecutorRegistration") - @Override - public int applySyncTransaction(@NonNull WindowContainerTransaction t, - @NonNull WindowContainerTransactionCallback callback) { - t.setTaskFragmentOrganizer(mInterface); - return super.applySyncTransaction(t, callback); - } - private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { @Override public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 4ea5ea5694fa..a902003f66dc 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -61,9 +61,7 @@ public void applyTransaction(@NonNull WindowContainerTransaction t) { * Apply multiple WindowContainer operations at once. * * Note that using this API requires the caller to hold - * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using - * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is - * created by itself. + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}. * * @param t The transaction to apply. * @param callback This transaction will use the synchronization scheme described in @@ -72,8 +70,7 @@ public void applyTransaction(@NonNull WindowContainerTransaction t) { * @return An ID for the sync operation which will later be passed to transactionReady callback. * This lets the caller differentiate overlapping sync operations. */ - @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS, - conditional = true) + @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull WindowContainerTransaction t, @NonNull WindowContainerTransactionCallback callback) { try { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 6970c7942a50..a6b9ba016793 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -177,7 +177,7 @@ public int applySyncTransaction(WindowContainerTransaction t, if (t == null) { throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); } - enforceTaskPermission("applySyncTransaction()", t); + enforceTaskPermission("applySyncTransaction()"); final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index f8c7207cefa7..318da074dc6f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -50,7 +50,6 @@ import android.window.TaskFragmentOrganizerToken; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import androidx.test.filters.SmallTest; @@ -227,19 +226,6 @@ public void testRegisterRemoteAnimations() { assertNull(mController.getRemoteAnimationDefinition(mIOrganizer)); } - @Test - public void testWindowContainerTransaction_setTaskFragmentOrganizer() { - mOrganizer.applyTransaction(mTransaction); - - assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); - - mTransaction = new WindowContainerTransaction(); - mOrganizer.applySyncTransaction( - mTransaction, mock(WindowContainerTransactionCallback.class)); - - assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); - } - @Test public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() throws RemoteException { From 5b10fc95d5c12e381ea3461e193d3850b584f211 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Thu, 23 Feb 2023 13:23:37 -0800 Subject: [PATCH 26/61] Remove Activity if it enters PiP without window This is to prevent malicious app entering PiP without being visible first, like blocking onResume from completion. Which in turn leaves the PiP window in limbo and non-interactable. Bug: 265293293 Test: atest PinnedStackTests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4fad1456409b79d6e649a29d5116a4fe3160bd21) Merged-In: I458a9508662e72a1adb9d9818105f2e9d7096d44 Change-Id: I458a9508662e72a1adb9d9818105f2e9d7096d44 --- .../core/java/com/android/server/wm/ActivityRecord.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b3c7510d97ed..e875123ff3e7 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1384,6 +1384,12 @@ void updatePictureInPictureMode(Rect targetRootTaskBounds, boolean forceUpdate) mLastReportedMultiWindowMode = inPictureInPictureMode; ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS, true /* ignoreVisibility */); + if (inPictureInPictureMode && findMainWindow() == null) { + // Prevent malicious app entering PiP without valid WindowState, which can in turn + // result a non-touchable PiP window since the InputConsumer for PiP requires it. + EventLog.writeEvent(0x534e4554, "265293293", -1, ""); + removeImmediately(); + } } } From e6a5e67ce46d235ee1a4291b899b9b96197effec Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Wed, 15 Feb 2023 20:39:44 +0100 Subject: [PATCH 27/61] [DO NOT MERGE] Wait for preloading images to complete before inflating notifications NotificationContentInflater waits on SysUiBg thread for images to load, with a timeout of 1000ms. Test: 1. Build a test app that posts MessagingStyle notifications with a huge image (8k+) set as data Uri. 2. SystemUi should not ANR 3. adb logcat | grep NotificationInlineImageCache - shows timeout/cancellation logs Bug: 252766417 Bug: 223859644 (cherry picked from commit 195043f40e46ddcd2fe534a9dac344792d39d91c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b9cd15ad8a2f87893164ad2ab518039bb0b61424) Merged-In: I341db60223214cf2282b5c0270e343e1ce95fa01 Change-Id: I341db60223214cf2282b5c0270e343e1ce95fa01 --- .../row/NotificationContentInflater.java | 15 +++- .../row/NotificationInlineImageCache.java | 23 ++++-- .../row/NotificationInlineImageResolver.java | 79 ++++++++++++++----- 3 files changed, 92 insertions(+), 25 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 1530e5238c67..dd71f581790a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -439,6 +439,7 @@ public RemoteViews getRemoteView() { CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); + return cancellationSignal; } @@ -711,6 +712,7 @@ public void setInflateSynchronously(boolean inflateSynchronously) { public static class AsyncInflationTask extends AsyncTask implements InflationCallback, InflationTask { + private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L; private final NotificationEntry mEntry; private final Context mContext; private final boolean mInflateSynchronously; @@ -804,7 +806,7 @@ protected InflationProgress doInBackground(Void... params) { recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState(); - return inflateSmartReplyViews( + InflationProgress result = inflateSmartReplyViews( inflationProgress, mReInflateFlags, mEntry, @@ -812,6 +814,11 @@ protected InflationProgress doInBackground(Void... params) { packageContext, previousSmartReplyState, mSmartRepliesInflater); + + // wait for image resolver to finish preloading + mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS); + + return result; } catch (Exception e) { mError = e; return null; @@ -846,6 +853,9 @@ private void handleError(Exception e) { mCallback.handleInflationException(mRow.getEntry(), new InflationException("Couldn't inflate contentViews" + e)); } + + // Cancel any image loading tasks, not useful any more + mRow.getImageResolver().cancelRunningTasks(); } @Override @@ -872,6 +882,9 @@ public void onAsyncInflationFinished(NotificationEntry entry) { // Notify the resolver that the inflation task has finished, // try to purge unnecessary cached entries. mRow.getImageResolver().purgeCache(); + + // Cancel any image loading tasks that have not completed at this point + mRow.getImageResolver().cancelRunningTasks(); } private static class RtlEnabledContext extends ContextWrapper { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java index 4b0e2ffd5d7f..75dfde89e14f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java @@ -23,8 +23,11 @@ import java.io.IOException; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A cache for inline images of image messages. @@ -57,12 +60,13 @@ public void preload(Uri uri) { } @Override - public Drawable get(Uri uri) { + public Drawable get(Uri uri, long timeoutMs) { Drawable result = null; try { - result = mCache.get(uri).get(); - } catch (InterruptedException | ExecutionException ex) { - Log.d(TAG, "get: Failed get image from " + uri); + result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException + | TimeoutException | CancellationException ex) { + Log.d(TAG, "get: Failed get image from " + uri + " " + ex); } return result; } @@ -73,6 +77,15 @@ public void purge() { mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey())); } + @Override + public void cancelRunningTasks() { + mCache.forEach((key, value) -> { + if (value.getStatus() != AsyncTask.Status.FINISHED) { + value.cancel(true); + } + }); + } + private static class PreloadImageTask extends AsyncTask { private final NotificationInlineImageResolver mResolver; @@ -87,7 +100,7 @@ protected Drawable doInBackground(Uri... uris) { try { drawable = mResolver.resolveImage(target); - } catch (IOException | SecurityException ex) { + } catch (Exception ex) { Log.d(TAG, "PreloadImageTask: Resolve failed from " + target, ex); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index 44ccb68cce4a..2caa434413f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; +import android.os.SystemClock; import android.util.Log; import com.android.internal.R; @@ -46,6 +47,9 @@ public class NotificationInlineImageResolver implements ImageResolver { private static final String TAG = NotificationInlineImageResolver.class.getSimpleName(); + // Timeout for loading images from ImageCache when calling from UI thread + private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L; + private final Context mContext; private final ImageCache mImageCache; private Set mWantedUriSet; @@ -111,30 +115,38 @@ protected int getMaxImageHeight() { * To resolve image from specified uri directly. If the resulting image is larger than the * maximum allowed size, scale it down. * @param uri Uri of the image. - * @return Drawable of the image. - * @throws IOException Throws if failed at resolving the image. + * @return Drawable of the image, or null if unable to load. */ - Drawable resolveImage(Uri uri) throws IOException { - return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight); + Drawable resolveImage(Uri uri) { + try { + return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight); + } catch (Exception ex) { + // Catch general Exception because ContentResolver can re-throw arbitrary Exception + // from remote process as a RuntimeException. See: Parcel#readException + Log.d(TAG, "resolveImage: Can't load image from " + uri, ex); + } + return null; } + /** + * Loads an image from the Uri. + * This method is synchronous and is usually called from the Main thread. + * It will time-out after MAX_UI_THREAD_TIMEOUT_MS. + * + * @param uri Uri of the target image. + * @return drawable of the image, null if loading failed/timeout + */ @Override public Drawable loadImage(Uri uri) { - Drawable result = null; - try { - if (hasCache()) { - // if the uri isn't currently cached, try caching it first - if (!mImageCache.hasEntry(uri)) { - mImageCache.preload((uri)); - } - result = mImageCache.get(uri); - } else { - result = resolveImage(uri); - } - } catch (IOException | SecurityException ex) { - Log.d(TAG, "loadImage: Can't load image from " + uri, ex); + return hasCache() ? loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS) : resolveImage(uri); + } + + private Drawable loadImageFromCache(Uri uri, long timeoutMs) { + // if the uri isn't currently cached, try caching it first + if (!mImageCache.hasEntry(uri)) { + mImageCache.preload((uri)); } - return result; + return mImageCache.get(uri, timeoutMs); } /** @@ -208,6 +220,30 @@ Set getWantedUriSet() { return mWantedUriSet; } + /** + * Wait for a maximum timeout for images to finish preloading + * @param timeoutMs total timeout time + */ + void waitForPreloadedImages(long timeoutMs) { + if (!hasCache()) { + return; + } + Set preloadedUris = getWantedUriSet(); + if (preloadedUris != null) { + // Decrement remaining timeout after each image check + long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs; + preloadedUris.forEach( + uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime())); + } + } + + void cancelRunningTasks() { + if (!hasCache()) { + return; + } + mImageCache.cancelRunningTasks(); + } + /** * A interface for internal cache implementation of this resolver. */ @@ -217,7 +253,7 @@ interface ImageCache { * @param uri The uri of the image. * @return Drawable of the image. */ - Drawable get(Uri uri); + Drawable get(Uri uri, long timeoutMs); /** * Set the image resolver that actually resolves image from specified uri. @@ -242,6 +278,11 @@ interface ImageCache { * Purge unnecessary entries in the cache. */ void purge(); + + /** + * Cancel all unfinished image loading tasks + */ + void cancelRunningTasks(); } } From 1e5ccd6410b41abb7a4b15774ddde7fa987ebeea Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Wed, 22 Feb 2023 09:38:55 +0100 Subject: [PATCH 28/61] [DO NOT MERGE] Prevent RemoteViews crashing SystemUi Catch canvas drawing exceptions caused by unsuported image sizes. Test: 1. Post a custom view notification with a layout containing an ImageView that references a 5k x 5k image 2. Add an App Widget to the home screen with that has the layout mentioned above as preview/initial layout. Bug: 268193777 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c3db1e4451490ddc7f6033a6ab7d54e71ebda9d8) Merged-In: Ib3bda769c499b4069b49c566b1b227f98f707a8a Change-Id: Ib3bda769c499b4069b49c566b1b227f98f707a8a Change-Id: Ibbaa234b663bc8e40d2a0a0f076a8676b6b1bc16 --- .../android/appwidget/AppWidgetHostView.java | 39 ++++++++++++++----- .../row/ExpandableNotificationRow.java | 4 +- .../ExpandableNotificationRowController.java | 9 ++++- .../row/NotificationContentView.java | 39 +++++++++++++++++++ ...NotificationEntryManagerInflationTest.java | 3 +- .../row/NotificationTestHelper.java | 4 +- 6 files changed, 83 insertions(+), 15 deletions(-) diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 8be2b4873c67..b0ffd7690377 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -31,6 +31,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PointF; import android.graphics.Rect; @@ -312,19 +313,26 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto super.onLayout(changed, left, top, right, bottom); } catch (final RuntimeException e) { Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); - removeViewInLayout(mView); - View child = getErrorView(); - prepareView(child); - addViewInLayout(child, 0, child.getLayoutParams()); - measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, - child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); - mView = child; - mViewMode = VIEW_MODE_ERROR; + handleViewError(); } } + /** + * Remove bad view and replace with error message view + */ + private void handleViewError() { + removeViewInLayout(mView); + View child = getErrorView(); + prepareView(child); + addViewInLayout(child, 0, child.getLayoutParams()); + measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, + child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); + mView = child; + mViewMode = VIEW_MODE_ERROR; + } + /** * Provide guidance about the size of this widget to the AppWidgetManager. The widths and * heights should correspond to the full area the AppWidgetHostView is given. Padding added by @@ -940,4 +948,15 @@ public void resetColorResources() { reapplyLastRemoteViews(); } } + + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + post(this::handleViewError); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index e11f8c685358..ac9ba67c83cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -71,6 +71,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; @@ -1574,7 +1575,8 @@ public void initialize( PeopleNotificationIdentifier peopleNotificationIdentifier, OnUserInteractionCallback onUserInteractionCallback, Optional bubblesManagerOptional, - NotificationGutsManager gutsManager) { + NotificationGutsManager gutsManager, + IStatusBarService statusBarService) { mEntry = entry; mAppName = appName; if (mMenuRow == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 0662a1eba8b6..e1f689867d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.plugins.FalsingManager; @@ -85,6 +86,7 @@ public class ExpandableNotificationRowController implements NodeController { private final boolean mAllowLongPress; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; private final Optional mBubblesManagerOptional; + private final IStatusBarService mStatusBarService; private final ExpandableNotificationRowDragController mDragController; @@ -113,7 +115,8 @@ public ExpandableNotificationRowController( FalsingCollector falsingCollector, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional bubblesManagerOptional, - ExpandableNotificationRowDragController dragController) { + ExpandableNotificationRowDragController dragController, + IStatusBarService statusBarService) { mView = view; mListContainer = listContainer; mActivatableNotificationViewController = activatableNotificationViewController; @@ -139,6 +142,7 @@ public ExpandableNotificationRowController( mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; mDragController = dragController; + mStatusBarService = statusBarService; } /** @@ -165,7 +169,8 @@ public void init(NotificationEntry entry) { mPeopleNotificationIdentifier, mOnUserInteractionCallback, mBubblesManagerOptional, - mNotificationGutsManager + mNotificationGutsManager, + mStatusBarService ); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 9cc484c02802..ac4f1563ce00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -21,10 +21,13 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.RemoteException; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -41,6 +44,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; @@ -127,6 +131,8 @@ public class NotificationContentView extends FrameLayout implements Notification private RemoteInputController mRemoteInputController; private Runnable mExpandedVisibleListener; private PeopleNotificationIdentifier mPeopleIdentifier; + private IStatusBarService mStatusBarService; + /** * List of listeners for when content views become inactive (i.e. not the showing view). */ @@ -180,6 +186,7 @@ public NotificationContentView(Context context, AttributeSet attrs) { mHybridGroupManager = new HybridGroupManager(getContext()); mSmartReplyConstants = Dependency.get(SmartReplyConstants.class); mSmartReplyController = Dependency.get(SmartReplyController.class); + mStatusBarService = Dependency.get(IStatusBarService.class); initView(); } @@ -2030,4 +2037,36 @@ public boolean requireRowToHaveOverlappingRendering() { } return false; } + + @Override + protected void dispatchDraw(Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + cancelNotification(e); + } + } + + private void cancelNotification(Exception exception) { + try { + setVisibility(GONE); + final StatusBarNotification sbn = mNotificationEntry.getSbn(); + if (mStatusBarService != null) { + // report notification inflation errors back up + // to notification delegates + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + exception.getMessage(), + sbn.getUser().getIdentifier()); + } + } catch (RemoteException ex) { + Log.e(TAG, "cancelNotification failed: " + ex); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index d3738f42e020..b00e80788521 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -271,7 +271,8 @@ public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder( new FalsingCollectorFake(), mPeopleNotificationIdentifier, Optional.of(mock(BubblesManager.class)), - mock(ExpandableNotificationRowDragController.class) + mock(ExpandableNotificationRowDragController.class), + mock(IStatusBarService.class) )); when(mNotificationRowComponentBuilder.activatableNotificationView(any())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index c5d1e3acb2b9..883f8e9bd7b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -43,6 +43,7 @@ import android.view.LayoutInflater; import android.widget.RemoteViews; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; @@ -490,7 +491,8 @@ private ExpandableNotificationRow generateRow( mPeopleNotificationIdentifier, mock(OnUserInteractionCallback.class), Optional.of(mock(BubblesManager.class)), - mock(NotificationGutsManager.class)); + mock(NotificationGutsManager.class), + mock(IStatusBarService.class)); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); From b775a436261929c3d0fc116523c101872aed587f Mon Sep 17 00:00:00 2001 From: Kevin Jeon Date: Fri, 17 Mar 2023 19:26:17 +0000 Subject: [PATCH 29/61] Grant MANAGE_USERS access to Traceur This change updates the privapp allowlist to grant the MANAGE_USERS permission to Traceur. This permission is needed to query admin user status, as Traceur shouldn't be able to start if the current user is not an admin. Test: Using ABTD, apply this change with ag/22119816 to verify that Traceur still works as intended (opening app, tracing, etc.). Bug: 262243665 Bug: 262244249 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f42db15239663604eb5d36edb04a0f9a04576568) Merged-In: I8e2174065b686c052cb080b3590ea4d89e7a7783 Change-Id: I8e2174065b686c052cb080b3590ea4d89e7a7783 --- data/etc/privapp-permissions-platform.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index fa27b68aba52..9608c9663fc8 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -552,6 +552,8 @@ applications that come with the platform + + From bd69b95a81243cc51ac51e56734dae9dd18dc3f1 Mon Sep 17 00:00:00 2001 From: Brian Lee Date: Fri, 17 Feb 2023 16:05:17 -0800 Subject: [PATCH 30/61] Check key intent for selectors and prohibited flags Bug: 265015796 Test: atest FrameworksServicesTests: com.android.server.accounts.AccountManagerServiceTest (cherry picked from commit e53a96304352e2965176c8d32ac1b504e52ef185) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5e01f68bdabe8aa7154e1ed936235b5304f4c0cd) Merged-In: Ie16f8654337bd75eaad3156817470674b4f0cee3 Change-Id: Ie16f8654337bd75eaad3156817470674b4f0cee3 --- .../accounts/AccountManagerService.java | 18 +++++++--- .../accounts/AccountManagerServiceTest.java | 36 +++++++++++++++++++ .../AccountManagerServiceTestFixtures.java | 5 ++- .../TestAccountType1Authenticator.java | 5 +-- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 8c80dfb94d53..c0aa36a0fb77 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4881,10 +4881,6 @@ protected boolean checkKeyIntent(int authUid, Bundle bundle) { if (intent.getClipData() == null) { intent.setClipData(ClipData.newPlainText(null, null)); } - intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)); final long bid = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); @@ -4931,7 +4927,19 @@ private boolean checkKeyIntentParceledCorrectly(Bundle bundle) { if (intent == null) { return (simulateIntent == null); } - return intent.filterEquals(simulateIntent); + if (!intent.filterEquals(simulateIntent)) { + return false; + } + + if (intent.getSelector() != simulateIntent.getSelector()) { + return false; + } + + int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; + return (simulateIntent.getFlags() & prohibitedFlags) == 0; } private boolean isExportedSystemActivity(ActivityInfo activityInfo) { diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 55619bc9c62a..b79e7873f20c 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -18,6 +18,7 @@ import static android.database.sqlite.SQLiteDatabase.deleteDatabase; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -705,6 +706,41 @@ public void testStartAddAccountSessionReturnWithValidIntent() throws Exception { assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK)); } + @SmallTest + public void testStartAddAccountSessionWhereAuthenticatorReturnsIntentWithProhibitedFlags() + throws Exception { + unlockSystemUser(); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = new ActivityInfo(); + resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.resolveActivityAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo); + when(mMockPackageManager.checkSignatures( + anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH); + + final CountDownLatch latch = new CountDownLatch(1); + Response response = new Response(latch, mMockAccountManagerResponse); + Bundle options = createOptionsWithAccountName( + AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE); + int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; + options.putInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, prohibitedFlags); + + mAms.startAddAccountSession( + response, // response + AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType + "authTokenType", + null, // requiredFeatures + true, // expectActivityLaunch + options); // optionsIn + waitForLatch(latch); + + verify(mMockAccountManagerResponse).onError( + eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), contains("invalid intent")); + } + @SmallTest public void testStartAddAccountSessionError() throws Exception { unlockSystemUser(); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java index 73f30d9f9e79..b98a6a891d55 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java @@ -17,9 +17,6 @@ import android.accounts.Account; -import java.util.ArrayList; -import java.util.List; - /** * Constants shared between test AccountAuthenticators and AccountManagerServiceTest. */ @@ -31,6 +28,8 @@ public final class AccountManagerServiceTestFixtures { "account_manager_service_test:account_status_token_key"; public static final String KEY_ACCOUNT_PASSWORD = "account_manager_service_test:account_password_key"; + public static final String KEY_INTENT_FLAGS = + "account_manager_service_test:intent_flags_key"; public static final String KEY_OPTIONS_BUNDLE = "account_manager_service_test:option_bundle_key"; public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com"; diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java index 8106364477d9..924443e9d5cf 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java +++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java @@ -24,8 +24,6 @@ import android.content.Intent; import android.os.Bundle; -import com.android.frameworks.servicestests.R; - import java.util.concurrent.atomic.AtomicInteger; /** @@ -270,11 +268,13 @@ public Bundle startAddAccountSession( String accountName = null; Bundle sessionBundle = null; String password = null; + int intentFlags = 0; if (options != null) { accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME); sessionBundle = options.getBundle( AccountManagerServiceTestFixtures.KEY_ACCOUNT_SESSION_BUNDLE); password = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_PASSWORD); + intentFlags = options.getInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, 0); } Bundle result = new Bundle(); @@ -302,6 +302,7 @@ public Bundle startAddAccountSession( intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, eventualActivityResultData); intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response); + intent.setFlags(intentFlags); result.putParcelable(AccountManager.KEY_INTENT, intent); } else { From e9b549b2617f0c1afa2ae48fc3c67b8dfd12cdba Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 21 Sep 2022 22:13:01 +0000 Subject: [PATCH 31/61] Handle invalid data during job loading. Catch exceptions that may be thrown if invalid data ended up in the persisted job file. Bug: 246541702 Bug: 246542132 Bug: 246542285 Bug: 246542330 Test: install test app with invalid job config, start app to schedule job, then reboot device (cherry picked from commit c98fb42b480b3beedc2d94de6110f50212c4aa0b) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7bdc1e8a3affd8534a829744001ef3ea26cce074) Merged-In: Id0ceba345942baf21177f687b8dd85ef001c0a9e Change-Id: Id0ceba345942baf21177f687b8dd85ef001c0a9e --- .../java/com/android/server/job/JobStore.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 7a2840709d15..7799c2ff04f6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -733,6 +733,10 @@ public void run() { } } catch (XmlPullParserException | IOException e) { Slog.wtf(TAG, "Error jobstore xml.", e); + } catch (Exception e) { + // Crashing at this point would result in a boot loop, so live with a general + // Exception for system stability's sake. + Slog.wtf(TAG, "Unexpected exception", e); } finally { if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. mPersistInfo.countAllJobsLoaded = numJobs; @@ -869,6 +873,9 @@ private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser) } catch (IOException e) { Slog.d(TAG, "Error I/O Exception.", e); return null; + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Constraints contained invalid data", e); + return null; } parser.next(); // Consume @@ -965,8 +972,14 @@ private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser) return null; } - PersistableBundle extras = PersistableBundle.restoreFromXml(parser); - jobBuilder.setExtras(extras); + final PersistableBundle extras; + try { + extras = PersistableBundle.restoreFromXml(parser); + jobBuilder.setExtras(extras); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Persisted extras contained invalid data", e); + return null; + } parser.nextTag(); // Consume final JobInfo builtJob; From 2a7b6bf72ebea1539c9ec86054d1db062a2274e4 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 7 Mar 2023 15:44:49 -0500 Subject: [PATCH 32/61] Allow filtering of services Test: ServiceListingTest Bug: 260570119 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a9c75de2b4ae92f4b7e7aade8433fd44ef376e11) Merged-In: Ib4740ba401667de62fa1a33334c2c1fbee25b760 Change-Id: Ib4740ba401667de62fa1a33334c2c1fbee25b760 --- .../applications/ServiceListing.java | 17 +++- .../applications/ServiceListingTest.java | 98 ++++++++++++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java index bd9e760acfda..c8bcabff1094 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.function.Predicate; /** * Class for managing services matching a given intent and requesting a given permission. @@ -51,12 +52,13 @@ public class ServiceListing { private final HashSet mEnabledServices = new HashSet<>(); private final List mServices = new ArrayList<>(); private final List mCallbacks = new ArrayList<>(); + private final Predicate mValidator; private boolean mListening; private ServiceListing(Context context, String tag, String setting, String intentAction, String permission, String noun, - boolean addDeviceLockedFlags) { + boolean addDeviceLockedFlags, Predicate validator) { mContentResolver = context.getContentResolver(); mContext = context; mTag = tag; @@ -65,6 +67,7 @@ private ServiceListing(Context context, String tag, mPermission = permission; mNoun = noun; mAddDeviceLockedFlags = addDeviceLockedFlags; + mValidator = validator; } public void addCallback(Callback callback) { @@ -137,7 +140,6 @@ public void reload() { final PackageManager pmWrapper = mContext.getPackageManager(); List installedServices = pmWrapper.queryIntentServicesAsUser( new Intent(mIntentAction), flags, user); - for (ResolveInfo resolveInfo : installedServices) { ServiceInfo info = resolveInfo.serviceInfo; @@ -148,6 +150,9 @@ public void reload() { + mPermission); continue; } + if (mValidator != null && !mValidator.test(info)) { + continue; + } mServices.add(info); } for (Callback callback : mCallbacks) { @@ -194,6 +199,7 @@ public static class Builder { private String mPermission; private String mNoun; private boolean mAddDeviceLockedFlags = false; + private Predicate mValidator; public Builder(Context context) { mContext = context; @@ -224,6 +230,11 @@ public Builder setNoun(String noun) { return this; } + public Builder setValidator(Predicate validator) { + mValidator = validator; + return this; + } + /** * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results @@ -236,7 +247,7 @@ public Builder setAddDeviceLockedFlags(boolean addDeviceLockedFlags) { public ServiceListing build() { return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun, - mAddDeviceLockedFlags); + mAddDeviceLockedFlags, mValidator); } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java index f7fd25b9fb7d..7ff0988c494d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java @@ -18,20 +18,35 @@ import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.provider.Settings; +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class ServiceListingTest { @@ -39,16 +54,97 @@ public class ServiceListingTest { private static final String TEST_INTENT = "com.example.intent"; private ServiceListing mServiceListing; + private Context mContext; + private PackageManager mPm; @Before public void setUp() { - mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application) + mPm = mock(PackageManager.class); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getPackageManager()).thenReturn(mPm); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setPermission("testPermission") + .build(); + } + + @Test + public void testValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) + .setTag("testTag") + .setSetting(TEST_SETTING) + .setNoun("testNoun") + .setIntentAction(TEST_INTENT) + .setValidator(info -> { + if (info.packageName.equals("pkg")) { + return true; + } + return false; + }) + .setPermission("testPermission") + .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(1); + assertThat(captor.getValue().get(0)).isEqualTo(s1); + } + + @Test + public void testNoValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; + + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + + mServiceListing = new ServiceListing.Builder(mContext) .setTag("testTag") .setSetting(TEST_SETTING) .setNoun("testNoun") .setIntentAction(TEST_INTENT) .setPermission("testPermission") .build(); + ServiceListing.Callback callback = mock(ServiceListing.Callback.class); + mServiceListing.addCallback(callback); + mServiceListing.reload(); + + verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt()); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(callback, times(1)).onServicesReloaded(captor.capture()); + + assertThat(captor.getValue().size()).isEqualTo(2); } @Test From 105804611f25ce1a62ffe55a5eaa9f7b867b06cb Mon Sep 17 00:00:00 2001 From: Alex Johnston Date: Wed, 8 Mar 2023 22:28:28 +0000 Subject: [PATCH 33/61] Enforce DevicePolicyManager.setUserControlDisabledPackages in AppStandbyController When deciding an app's standby bucket, check if the app has its user control disabled by an IT admin. If so, the app should be the exempted restricted bucket. Bug: 272042183 Test: atest AppStandbyControllerTests (cherry picked from commit 269fcb6873dee199dd8023831f882aafff1f6291) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3dbab873d6d8f78c4d498a575ad37fd0dc20efbe) Merged-In: I4279dc37f0e17aedb1c2a87468478248443a253e Change-Id: I4279dc37f0e17aedb1c2a87468478248443a253e --- .../server/usage/AppStandbyInternal.java | 2 + .../server/usage/AppStandbyController.java | 40 +++++++++++++++++++ .../app/admin/DevicePolicyManager.java | 3 +- .../app/usage/UsageStatsManagerInternal.java | 10 +++++ .../DevicePolicyManagerService.java | 2 +- .../android/server/devicepolicy/Owners.java | 18 ++++++++- .../DevicePolicyManagerServiceTestable.java | 3 +- .../usage/AppStandbyControllerTests.java | 38 ++++++++++++++++++ .../server/usage/UsageStatsService.java | 5 +++ 9 files changed, 116 insertions(+), 5 deletions(-) diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 8c06338560bf..4be7b3f5f86e 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -157,6 +157,8 @@ void restrictApp(@NonNull String packageName, int userId, int mainReason, void setActiveAdminApps(Set adminPkgs, int userId); + void setAdminProtectedPackages(Set packageNames, int userId); + void onAdminDataAvailable(); void clearCarrierPrivilegedApps(); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 5358dbfc0b1e..dd516c51205b 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -247,6 +247,10 @@ static class Lock {} @GuardedBy("mActiveAdminApps") private final SparseArray> mActiveAdminApps = new SparseArray<>(); + /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */ + @GuardedBy("mAdminProtectedPackages") + private final SparseArray> mAdminProtectedPackages = new SparseArray<>(); + /** * Set of system apps that are headless (don't have any declared activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. @@ -1088,6 +1092,9 @@ public void onUserRemoved(int userId) { synchronized (mActiveAdminApps) { mActiveAdminApps.remove(userId); } + synchronized (mAdminProtectedPackages) { + mAdminProtectedPackages.remove(userId); + } } } @@ -1177,6 +1184,10 @@ private int getAppMinBucket(String packageName, int appId, int userId) { return STANDBY_BUCKET_EXEMPTED; } + if (isAdminProtectedPackages(packageName, userId)) { + return STANDBY_BUCKET_EXEMPTED; + } + if (isActiveNetworkScorer(packageName)) { return STANDBY_BUCKET_EXEMPTED; } @@ -1583,6 +1594,17 @@ boolean isActiveDeviceAdmin(String packageName, int userId) { } } + private boolean isAdminProtectedPackages(String packageName, int userId) { + synchronized (mAdminProtectedPackages) { + if (mAdminProtectedPackages.contains(UserHandle.USER_ALL) + && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) { + return true; + } + return mAdminProtectedPackages.contains(userId) + && mAdminProtectedPackages.get(userId).contains(packageName); + } + } + @Override public void addActiveDeviceAdmin(String adminPkg, int userId) { synchronized (mActiveAdminApps) { @@ -1606,6 +1628,17 @@ public void setActiveAdminApps(Set adminPkgs, int userId) { } } + @Override + public void setAdminProtectedPackages(Set packageNames, int userId) { + synchronized (mAdminProtectedPackages) { + if (packageNames == null || packageNames.isEmpty()) { + mAdminProtectedPackages.remove(userId); + } else { + mAdminProtectedPackages.put(userId, packageNames); + } + } + } + @Override public void onAdminDataAvailable() { mAdminDataAvailableLatch.countDown(); @@ -1628,6 +1661,13 @@ Set getActiveAdminAppsForTest(int userId) { } } + @VisibleForTesting + Set getAdminProtectedPackagesForTest(int userId) { + synchronized (mAdminProtectedPackages) { + return mAdminProtectedPackages.get(userId); + } + } + /** * Returns {@code true} if the supplied package is the device provisioning app. Otherwise, * returns {@code false}. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8fd953e1aae3..117e3f6f143d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -13573,7 +13573,8 @@ public boolean startViewCalendarEventInManagedProfile(long eventId, long start, /** * Called by Device owner to disable user control over apps. User will not be able to clear - * app data or force-stop packages. + * app data or force-stop packages. Packages with user control disabled are exempted from + * App Standby Buckets. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param packages The package names for the apps. diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index b2226d1e0fa3..b55971848720 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -198,6 +198,16 @@ public abstract void reportLocusUpdate(@NonNull ComponentName activity, @UserIdI */ public abstract void setActiveAdminApps(Set adminApps, int userId); + /** + * Called by DevicePolicyManagerService to inform about the protected packages for a user. + * User control will be disabled for protected packages. + * + * @param packageNames the set of protected packages for {@code userId}. + * @param userId the userId to which the protected packages belong. + */ + public abstract void setAdminProtectedPackages(@Nullable Set packageNames, + @UserIdInt int userId); + /** * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and * pushed to UsageStatsService. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a1eee6129ebe..9e375a0ba265 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1341,7 +1341,7 @@ Resources getResources() { Owners newOwners() { return new Owners(getUserManager(), getUserManagerInternal(), getPackageManagerInternal(), getActivityTaskManagerInternal(), - getActivityManagerInternal()); + getActivityManagerInternal(), getUsageStatsManagerInternal()); } UserManager getUserManager() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 3584728a2e62..6849a4db9c69 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -24,6 +24,7 @@ import android.app.admin.DevicePolicyManager.DeviceOwnerType; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -123,6 +124,7 @@ class Owners { private final PackageManagerInternal mPackageManagerInternal; private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; + private final UsageStatsManagerInternal mUsageStatsManagerInternal; private boolean mSystemReady; @@ -155,9 +157,11 @@ public Owners(UserManager userManager, UserManagerInternal userManagerInternal, PackageManagerInternal packageManagerInternal, ActivityTaskManagerInternal activityTaskManagerInternal, - ActivityManagerInternal activitykManagerInternal) { + ActivityManagerInternal activitykManagerInternal, + UsageStatsManagerInternal usageStatsManagerInternal) { this(userManager, userManagerInternal, packageManagerInternal, - activityTaskManagerInternal, activitykManagerInternal, new Injector()); + activityTaskManagerInternal, activitykManagerInternal, + usageStatsManagerInternal, new Injector()); } @VisibleForTesting @@ -166,12 +170,14 @@ public Owners(UserManager userManager, PackageManagerInternal packageManagerInternal, ActivityTaskManagerInternal activityTaskManagerInternal, ActivityManagerInternal activityManagerInternal, + UsageStatsManagerInternal usageStatsManagerInternal, Injector injector) { mUserManager = userManager; mUserManagerInternal = userManagerInternal; mPackageManagerInternal = packageManagerInternal; mActivityTaskManagerInternal = activityTaskManagerInternal; mActivityManagerInternal = activityManagerInternal; + mUsageStatsManagerInternal = usageStatsManagerInternal; mInjector = injector; } @@ -226,6 +232,8 @@ void load() { mDeviceOwnerProtectedPackages.entrySet()) { mPackageManagerInternal.setDeviceOwnerProtectedPackages( entry.getKey(), entry.getValue()); + mUsageStatsManagerInternal.setAdminProtectedPackages( + new ArraySet(entry.getValue()), UserHandle.USER_ALL); } } } @@ -359,6 +367,8 @@ void clearDeviceOwner() { if (protectedPackages != null) { mPackageManagerInternal.setDeviceOwnerProtectedPackages( mDeviceOwner.packageName, new ArrayList<>()); + mUsageStatsManagerInternal.setAdminProtectedPackages( + Collections.emptySet(), UserHandle.USER_ALL); } mDeviceOwner = null; mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -416,6 +426,8 @@ void transferDeviceOwnership(ComponentName target) { if (previousProtectedPackages != null) { mPackageManagerInternal.setDeviceOwnerProtectedPackages( mDeviceOwner.packageName, new ArrayList<>()); + mUsageStatsManagerInternal.setAdminProtectedPackages( + Collections.emptySet(), UserHandle.USER_ALL); } // We don't set a name because it's not used anyway. // See DevicePolicyManagerService#getDeviceOwnerName @@ -684,6 +696,8 @@ void setDeviceOwnerProtectedPackages(String packageName, List protectedP mDeviceOwnerProtectedPackages.put(packageName, protectedPackages); mPackageManagerInternal.setDeviceOwnerProtectedPackages(packageName, protectedPackages); + mUsageStatsManagerInternal.setAdminProtectedPackages( + new ArraySet(protectedPackages), UserHandle.USER_ALL); writeDeviceOwner(); } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 61d7ede98f45..b509630de13e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -69,7 +69,8 @@ public static class OwnersTestable extends Owners { public OwnersTestable(MockSystemServices services) { super(services.userManager, services.userManagerInternal, services.packageManagerInternal, services.activityTaskManagerInternal, - services.activityManagerInternal, new MockInjector(services)); + services.activityManagerInternal, services.usageStatsManagerInternal, + new MockInjector(services)); } static class MockInjector extends Injector { diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 9e46e1f2be92..955f7f2d5e95 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -152,6 +152,9 @@ public class AppStandbyControllerTests { private static final String ADMIN_PKG2 = "com.android.admin2"; private static final String ADMIN_PKG3 = "com.android.admin3"; + private static final String ADMIN_PROTECTED_PKG = "com.android.admin.protected"; + private static final String ADMIN_PROTECTED_PKG2 = "com.android.admin.protected2"; + private static final long MINUTE_MS = 60 * 1000; private static final long HOUR_MS = 60 * MINUTE_MS; private static final long DAY_MS = 24 * HOUR_MS; @@ -1631,6 +1634,19 @@ public void isActiveDeviceAdmin() throws Exception { assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); } + @Test + public void testSetAdminProtectedPackages() { + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + } + @Test @FlakyTest(bugId = 185169504) public void testUserInteraction_CrossProfile() throws Exception { @@ -2025,6 +2041,28 @@ private void setActiveAdmins(int userId, String... admins) { mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId); } + private void setAdminProtectedPackages(int userId, String... packageNames) { + Set adminProtectedPackages = packageNames != null ? new ArraySet<>( + Arrays.asList(packageNames)) : null; + mController.setAdminProtectedPackages(adminProtectedPackages, userId); + } + + private void assertAdminProtectedPackagesForTest(int userId, String... packageNames) { + final Set actualAdminProtectedPackages = + mController.getAdminProtectedPackagesForTest(userId); + if (packageNames == null) { + if (actualAdminProtectedPackages != null && !actualAdminProtectedPackages.isEmpty()) { + fail("Admin protected packages should be null; " + getAdminAppsStr(userId, + actualAdminProtectedPackages)); + } + return; + } + assertEquals(packageNames.length, actualAdminProtectedPackages.size()); + for (String adminProtectedPackage : packageNames) { + assertTrue(actualAdminProtectedPackages.contains(adminProtectedPackage)); + } + } + private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception { rearmLatch(pkg); mController.setAppStandbyBucket(pkg, user, bucket, reason); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index ac1fcce20dc0..8872e1a1bcbd 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2494,6 +2494,11 @@ public void setActiveAdminApps(Set packageNames, int userId) { mAppStandby.setActiveAdminApps(packageNames, userId); } + @Override + public void setAdminProtectedPackages(Set packageNames, int userId) { + mAppStandby.setAdminProtectedPackages(packageNames, userId); + } + @Override public void onAdminDataAvailable() { mAppStandby.onAdminDataAvailable(); From 00d135a670ad0529e6b80b6e47e55374ede7f4d4 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 24 Mar 2023 16:15:24 +0000 Subject: [PATCH 34/61] [RESTRICT AUTOMERGE] Add BubbleMetadata detection to block FSI Bug: 274759612 Test: atest NotificationInterruptStateProviderImplTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1bc1be92ce0d8bd8abd9efa13e85ac0d33556a3b) Merged-In: I40e1aa6377b8a60d91cb2f4189df1e9a4a4578a2 Change-Id: I40e1aa6377b8a60d91cb2f4189df1e9a4a4578a2 --- ...NotificationInterruptStateProviderImpl.java | 15 +++++++++++++++ ...ficationInterruptStateProviderImplTest.java | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index d2bb35721b88..44598f8016d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -18,6 +18,7 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; +import android.app.Notification; import android.app.NotificationManager; import android.content.ContentResolver; import android.content.Context; @@ -221,6 +222,20 @@ public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) { return false; } + // If the notification has suppressive BubbleMetadata, block FSI and warn. + Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata(); + if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) { + // b/274759612: Detect and report an event when a notification has both an FSI and a + // suppressive BubbleMetadata, and now correctly block the FSI from firing. + final int uid = entry.getSbn().getUid(); + android.util.EventLog.writeEvent(0x534e4554, "274759612", uid, "bubbleMetadata"); + if (DEBUG) { + Log.w(TAG, "No FullScreenIntent: WARNING: BubbleMetadata may prevent HUN: " + + entry.getKey()); + } + return false; + } + // If the screen is off, then launch the FullScreenIntent if (!mPowerManager.isInteractive()) { if (DEBUG) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index fc4f5db9eef6..f0adf4ebeb7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -436,9 +436,27 @@ public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteExceptio .isFalse(); } + + @Test + public void testShouldNotFullScreen_isSuppressedByBubbleMetadata() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo") + .setSuppressNotification(true).build(); + entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mDreamManager.isDreaming()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(KEYGUARD); + + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + } + @Test public void testShouldFullScreen_notInteractive() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo") + .setSuppressNotification(false).build(); + entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata); when(mPowerManager.isInteractive()).thenReturn(false); when(mDreamManager.isDreaming()).thenReturn(false); when(mStatusBarStateController.getState()).thenReturn(SHADE); From 06b718e7e7112d2fa91e3b1132c0bcbca695d540 Mon Sep 17 00:00:00 2001 From: Mark Renouf Date: Wed, 22 Feb 2023 15:14:08 +0000 Subject: [PATCH 35/61] Prevent sharesheet from previewing unowned URIs Bug: 261036568 Test: manually via supplied tool (see bug) (cherry picked from commit 3062b80fb28014a7482d5fa8b2a5c852134a5845) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:08809fa8c938ccc6f0cd21036fcc464a96d93384) Merged-In: I21accf6f753d2f676f1602d6e1ce829c5ef29e9a Change-Id: I21accf6f753d2f676f1602d6e1ce829c5ef29e9a --- .../android/internal/app/ChooserActivity.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index ea22d25d323a..d39b8b7f207b 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -16,6 +16,8 @@ package com.android.internal.app; +import static android.content.ContentProvider.getUserIdFromUri; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.animation.Animator; @@ -149,6 +151,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * The Chooser Activity handles intent resolution specifically for sharing intents - @@ -1375,7 +1378,7 @@ private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater ImageView previewThumbnailView = contentPreviewLayout.findViewById( R.id.content_preview_thumbnail); - if (previewThumbnail == null) { + if (!validForContentPreview(previewThumbnail)) { previewThumbnailView.setVisibility(View.GONE); } else { mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false); @@ -1403,6 +1406,10 @@ private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + if (!validForContentPreview(uri)) { + contentPreviewLayout.setVisibility(View.GONE); + return contentPreviewLayout; + } imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); @@ -1412,7 +1419,7 @@ private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater List uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); List imageUris = new ArrayList<>(); for (Uri uri : uris) { - if (isImageType(resolver.getType(uri))) { + if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) { imageUris.add(uri); } } @@ -1521,9 +1528,16 @@ private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM); + if (!validForContentPreview(uri)) { + contentPreviewLayout.setVisibility(View.GONE); + return contentPreviewLayout; + } loadFileUriIntoView(uri, contentPreviewLayout); } else { List uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + uris = uris.stream() + .filter(ChooserActivity::validForContentPreview) + .collect(Collectors.toList()); int uriCount = uris.size(); if (uriCount == 0) { @@ -1577,6 +1591,24 @@ private void loadFileUriIntoView(final Uri uri, final View parent) { } } + /** + * Indicate if the incoming content URI should be allowed. + * + * @param uri the uri to test + * @return true if the URI is allowed for content preview + */ + private static boolean validForContentPreview(Uri uri) throws SecurityException { + if (uri == null) { + return false; + } + int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT); + if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) { + Log.e(TAG, "dropped invalid content URI belonging to user " + userId); + return false; + } + return true; + } + @VisibleForTesting protected boolean isImageType(String mimeType) { return mimeType != null && mimeType.startsWith("image/"); From 1d6e5628b9c2f9818ecc5064471cbb7961d4481d Mon Sep 17 00:00:00 2001 From: Lucas Lin Date: Fri, 3 Mar 2023 08:13:50 +0000 Subject: [PATCH 36/61] Sanitize VPN label to prevent HTML injection This commit will try to sanitize the content of VpnDialog. This commit creates a function which will try to sanitize the VPN label, if the sanitized VPN label is different from the original one, which means the VPN label might contain HTML tag or the VPN label violates the words restriction(may contain some wording which will mislead the user). For this kind of case, show the package name instead of the VPN label to prevent misleading the user. The malicious VPN app might be able to add a large number of line breaks with HTML in order to hide the system-displayed text from the user in the connection request dialog. Thus, sanitizing the content of the dialog is needed. Bug: 204554636 Test: N/A (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:2178216b98bf9865edee198f45192f0b883624ab) Merged-In: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d Change-Id: I8eb890fd2e5797d8d6ab5b12f9c628bc9616081d --- packages/VpnDialogs/res/values/strings.xml | 28 ++++++++++ .../com/android/vpndialogs/ConfirmDialog.java | 53 +++++++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index f971a0916837..a85b8e4ff553 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -100,4 +100,32 @@ without any consequences. [CHAR LIMIT=20] --> Dismiss + + + %1$s… ( + %2$s) + + + + + %1$s ( + %2$s) + diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index fb2367843fc1..2b3202e0a982 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -40,12 +40,18 @@ public class ConfirmDialog extends AlertActivity implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; + // Usually the label represents the app name, 150 code points might be enough to display the app + // name, and 150 code points won't cover the warning message from VpnDialog. + static final int MAX_VPN_LABEL_LENGTH = 150; + @VpnManager.VpnType private final int mVpnType; private String mPackage; private VpnManager mVm; + private View mView; + public ConfirmDialog() { this(VpnManager.TYPE_VPN_SERVICE); } @@ -54,6 +60,42 @@ public ConfirmDialog(@VpnManager.VpnType int vpnType) { mVpnType = vpnType; } + /** + * This function will use the string resource to combine the VPN label and the package name. + * + * If the VPN label violates the length restriction, the first 30 code points of VPN label and + * the package name will be returned. Or return the VPN label and the package name directly if + * the VPN label doesn't violate the length restriction. + * + * The result will be something like, + * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app) + * if the VPN label violates the length restriction. + * or + * - VpnLabelWith<br>HtmlTag (com.vpn.app) + * if the VPN label doesn't violate the length restriction. + * + */ + private String getSimplifiedLabel(String vpnLabel, String packageName) { + if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) { + return getString(R.string.sanitized_vpn_label_with_ellipsis, + vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)), + packageName); + } + + return getString(R.string.sanitized_vpn_label, vpnLabel, packageName); + } + + protected String getSanitizedVpnLabel(String vpnLabel, String packageName) { + final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel); + final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0, + sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH; + if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) { + return getSimplifiedLabel(sanitizedVpnLabel, packageName); + } + + return sanitizedVpnLabel; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -75,15 +117,16 @@ protected void onCreate(Bundle savedInstanceState) { finish(); return; } - View view = View.inflate(this, R.layout.confirm, null); - ((TextView) view.findViewById(R.id.warning)).setText( - Html.fromHtml(getString(R.string.warning, getVpnLabel()), - this, null /* tagHandler */)); + mView = View.inflate(this, R.layout.confirm, null); + ((TextView) mView.findViewById(R.id.warning)).setText( + Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel( + getVpnLabel().toString(), mPackage)), + this /* imageGetter */, null /* tagHandler */)); mAlertParams.mTitle = getText(R.string.prompt); mAlertParams.mPositiveButtonText = getText(android.R.string.ok); mAlertParams.mPositiveButtonListener = this; mAlertParams.mNegativeButtonText = getText(android.R.string.cancel); - mAlertParams.mView = view; + mAlertParams.mView = mView; setupAlert(); getWindow().setCloseOnTouchOutside(false); From 9f103962b51d5eec79128aee222c71764d3e55b5 Mon Sep 17 00:00:00 2001 From: Michael Groover Date: Fri, 31 Mar 2023 21:31:22 +0000 Subject: [PATCH 37/61] Limit the number of supported v1 and v2 signers The v1 and v2 APK Signature Schemes support multiple signers; this was intended to allow multiple entities to sign an APK. Previously, the platform had no limits placed on the number of signers supported in an APK, but this commit sets a hard limit of 10 supported signers for these signature schemes to ensure a large number of signers does not place undue burden on the platform. Bug: 266580022 Test: Manually verified the platform only allowed an APK with the maximum number of supported signers. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6f6ee8a55f37c2b8c0df041b2bd53ec928764597) Merged-In: I6aa86b615b203cdc69d58a593ccf8f18474ca091 Change-Id: I6aa86b615b203cdc69d58a593ccf8f18474ca091 --- .../util/apk/ApkSignatureSchemeV2Verifier.java | 10 ++++++++++ core/java/android/util/jar/StrictJarVerifier.java | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java index f74990a82327..0f1ab7fd1c34 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java @@ -74,6 +74,11 @@ public class ApkSignatureSchemeV2Verifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; + /** + * The maximum number of signers supported by the v2 APK signature scheme. + */ + private static final int MAX_V2_SIGNERS = 10; + /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * @@ -182,6 +187,11 @@ private static VerifiedSigner verify( } while (signers.hasRemaining()) { signerCount++; + if (signerCount > MAX_V2_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS + + " signers"); + } try { ByteBuffer signer = getLengthPrefixedSlice(signers); X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java index 45254908c5c9..a6aca330d323 100644 --- a/core/java/android/util/jar/StrictJarVerifier.java +++ b/core/java/android/util/jar/StrictJarVerifier.java @@ -78,6 +78,11 @@ class StrictJarVerifier { "SHA1", }; + /** + * The maximum number of signers supported by the JAR signature scheme. + */ + private static final int MAX_JAR_SIGNERS = 10; + private final String jarName; private final StrictJarManifest manifest; private final HashMap metaEntries; @@ -293,10 +298,16 @@ synchronized boolean readCertificates() { return false; } + int signerCount = 0; Iterator it = metaEntries.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { + if (++signerCount > MAX_JAR_SIGNERS) { + throw new SecurityException( + "APK Signature Scheme v1 only supports a maximum of " + MAX_JAR_SIGNERS + + " signers"); + } verifyCertificate(key); it.remove(); } From 4fc255919c80bc1c994cd995de85d06708574fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 13 Apr 2023 17:58:22 +0200 Subject: [PATCH 38/61] Grant URI permissions to the CallStyle-related ones This will also verify that the caller app can actually grant them. Fix: 274592467 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4dee5aab12e95cd8b4d663ad050f07b0f2433596) Merged-In: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc Change-Id: I83429f9e63e51c615a6e3f03befb76bb5b8ea7fc --- core/java/android/app/Notification.java | 8 +++++++ .../NotificationManagerServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c723975d996d..0f41c7175b17 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2837,6 +2837,14 @@ public void visitUris(@NonNull Consumer visitor) { } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } 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 c45fb019e4c7..387d29492665 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4463,6 +4463,29 @@ public void testVisitUris() throws Exception { verify(visitor, times(1)).accept(eq(backgroundImage)); } + @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); From bc911be3d95dde3e114c3d834bc142b1895dc2ab Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 8 Feb 2023 01:04:46 +0000 Subject: [PATCH 39/61] Only allow NEW_TASK flag when adjusting pending intents Bug: 243794108 Test: atest CtsSecurityBulletinHostTestCases:android.security.cts.CVE_2023_20918 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c62d2e1021a030f4f0ae5fcfc8fe8e0875fa669f) Merged-In: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b Change-Id: I5d329beecef1902c36704e93d0bc5cb60d0e2f5b --- core/java/android/app/ActivityOptions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e76f89ce9461..479ef24fe450 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,6 +20,8 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; import android.annotation.IntDef; @@ -1554,7 +1556,9 @@ public void setPendingIntentLaunchFlags(@android.content.Intent.Flags int flags) * @hide */ public int getPendingIntentLaunchFlags() { - return mPendingIntentLaunchFlags; + // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 + return mPendingIntentLaunchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); } /** From 370f4959df76484836a4ec37c1062b7b9139ce4a Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 28 Mar 2023 13:15:04 -0700 Subject: [PATCH 40/61] Dismiss keyguard when simpin auth'd and... security method is none. This is mostly to fix the case where we auth sim pin in the set up wizard and it goes straight to keyguard instead of the setup wizard activity. This works with the prevent bypass keyguard flag because the device should be noe secure in this case. Fixes: 222446076 Test: turn locked sim on, which opens the sim pin screen. Auth the screen and observe that keyguard is not shown. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:48fa9bef3451e4a358c941af5b230f99881c5cb6) Cherry-picking this CL as a security fix Bug: 222446076 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:65ea56f54c059584eb27ec53d486dba8161316ab) Merged-In: Id302c41f63028bc6dd58ba686e23d73565de9675 Change-Id: Id302c41f63028bc6dd58ba686e23d73565de9675 --- .../android/keyguard/KeyguardSecurityContainerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d501ccdd5103..97ed829380f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -426,7 +426,7 @@ public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetU case SimPuk: // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled( + if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser())) { finish = true; eventSubtype = BOUNCER_DISMISS_SIM; From 26cde5626d8ac952a9fb9c7162240267ea899f13 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 21 Apr 2023 15:39:22 +0000 Subject: [PATCH 41/61] Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS. Also added the person URIs in the test, since they weren't being checked. Test: atest NotificationManagerServiceTest & tested with POC from bug Bug: 276729064 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:43b1711332763788c7abf05c3baa931296c45bbb) Merged-In: I848545f7aee202495c515f47a32871a2cb6ae707 Change-Id: I848545f7aee202495c515f47a32871a2cb6ae707 --- core/java/android/app/Notification.java | 11 +++++++ .../NotificationManagerServiceTest.java | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0f41c7175b17..b4670b7cb0b4 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2807,6 +2807,17 @@ public void visitUris(@NonNull Consumer visitor) { if (person != null) { visitor.accept(person.getIconUri()); } + + final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) + extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + if (history != null) { + for (int i = 0; i < history.length; i++) { + RemoteInputHistoryItem item = history[i]; + if (item.getUri() != null) { + visitor.accept(item.getUri()); + } + } + } } if (isStyle(MessagingStyle.class) && extras != null) { 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 387d29492665..5713f689e17a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -115,6 +115,7 @@ import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; +import android.app.RemoteInputHistoryItem; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; @@ -4446,10 +4447,36 @@ public void updateUriPermissions_posterDoesNotOwnUri() throws Exception { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("People List Person 1") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("People List Person 2") + .setIcon(personIcon3) + .build(); + final Uri historyUri1 = Uri.parse("content://com.example/history1"); + final Uri historyUri2 = Uri.parse("content://com.example/history2"); + final RemoteInputHistoryItem historyItem1 = new RemoteInputHistoryItem(null, historyUri1, + "a"); + final RemoteInputHistoryItem historyItem2 = new RemoteInputHistoryItem(null, historyUri2, + "b"); Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); + extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + new RemoteInputHistoryItem[]{historyItem1, historyItem2}); Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") @@ -4461,6 +4488,11 @@ public void testVisitUris() throws Exception { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + verify(visitor, times(1)).accept(eq(historyUri1)); + verify(visitor, times(1)).accept(eq(historyUri2)); } @Test From 33e0680b624ae693914bf521773fff09bee8a1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Fri, 21 Apr 2023 09:45:07 +0000 Subject: [PATCH 42/61] Truncate ShortcutInfo Id Creating Conversation with a ShortcutId longer than 65_535 (max unsigned short), we did not save the conversation settings into the notification_policy.xml due to a restriction in FastDataOutput. This put us to a state where the user changing the importance or turning off the notifications for the given conversation had no effect on notification behavior. Fixes: 273729476 Test: atest ShortcutManagerTest2 Test: Create a test app which creates a Conversation with a long shortcutId. Go to the Conversation Settings and turn off Notifications. Post a new Notification to this Conversation and see if it is displayed. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ab0c8ac5b47509a71f27c4e5e9ce104d51bab0a8) Merged-In: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 Change-Id: I2617de6f9e8a7dbfd8fbeff589a7d592f00d87c5 --- .../java/android/content/pm/ShortcutInfo.java | 20 ++++++++++++++++--- .../server/pm/ShortcutManagerTest2.java | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index a264bebb5d88..3308d73d1d79 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -276,6 +276,12 @@ public final class ShortcutInfo implements Parcelable { */ public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; + /** + * The maximum length of Shortcut ID. IDs will be truncated at this limit. + * @hide + */ + public static final int MAX_ID_LENGTH = 1000; + /** @hide */ @IntDef(prefix = { "DISABLED_REASON_" }, value = { DISABLED_REASON_NOT_DISABLED, @@ -453,8 +459,7 @@ public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReas private ShortcutInfo(Builder b) { mUserId = b.mContext.getUserId(); - - mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided")); // Note we can't do other null checks here because SM.updateShortcuts() takes partial // information. @@ -558,6 +563,14 @@ private static Person[] clonePersons(Person[] persons) { return ret; } + @NonNull + private static String getSafeId(@NonNull String id) { + if (id.length() > MAX_ID_LENGTH) { + return id.substring(0, MAX_ID_LENGTH); + } + return id; + } + /** * Throws if any of the mandatory fields is not set. * @@ -2141,7 +2154,8 @@ private ShortcutInfo(Parcel source) { final ClassLoader cl = getClass().getClassLoader(); mUserId = source.readInt(); - mId = source.readString8(); + mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(), + "Shortcut ID must be provided")); mPackageName = source.readString8(); mActivity = source.readParcelable(cl); mFlags = source.readInt(); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 90a127701505..27091b7d546a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -53,6 +53,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.util.Collections; import java.util.Locale; /** @@ -223,6 +224,15 @@ public void testShortcutInfoMissingMandatoryFields() { }); } + public void testShortcutIdTruncated() { + ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), + String.join("", Collections.nCopies(Short.MAX_VALUE, "s"))).build(); + + assertTrue( + "id must be truncated to MAX_ID_LENGTH", + si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH); + } + public void testShortcutInfoParcel() { setCaller(CALLING_PACKAGE_1, USER_10); ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext) From 0a45958dfc6838108055d014cac8ed6cf5a0a233 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:36:05 +0000 Subject: [PATCH 43/61] Visit URIs in landscape/portrait custom remote views. Bug: 277740848 Test: atest RemoteViewsTest NotificationManagerServiceTest & tested with POC from bug (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b4692946c10d11c1e935869e11dc709a9cdcba69) Merged-In: I7d3d35df0ec38945019f71755bed8797b7af4517 Change-Id: I7d3d35df0ec38945019f71755bed8797b7af4517 --- core/java/android/widget/RemoteViews.java | 6 ++ .../src/android/widget/RemoteViewsTest.java | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 1784dcfc505f..fe6eb32f0158 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -709,6 +709,12 @@ public void visitUris(@NonNull Consumer visitor) { mActions.get(i).visitUris(visitor); } } + if (mLandscape != null) { + mLandscape.visitUris(visitor); + } + if (mPortrait != null) { + mPortrait.visitUris(visitor); + } } private static void visitIconUri(Icon icon, @NonNull Consumer visitor) { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 059c764213bc..6cdf72071194 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -20,6 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -29,6 +33,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Parcel; @@ -50,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * Tests for RemoteViews. @@ -499,4 +506,61 @@ public ActivityOptions createSharedElementActivityOptions( return null; } } + + @Test + public void visitUris() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + + final Uri imageUri = Uri.parse("content://media/image"); + final Icon icon1 = Icon.createWithContentUri("content://media/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://media/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://media/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://media/icon4"); + views.setImageViewUri(R.id.image, imageUri); + views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUri)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + + @Test + public void visitUris_separateOrientation() { + final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://landscape/image"); + final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4"); + landscape.setImageViewUri(R.id.image, imageUriL); + landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews portrait = new RemoteViews(mPackage, 33); + final Uri imageUriP = Uri.parse("content://portrait/image"); + final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1"); + final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2"); + final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3"); + final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4"); + portrait.setImageViewUri(R.id.image, imageUriP); + portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P); + + RemoteViews views = new RemoteViews(landscape, portrait); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriP)); + verify(visitor, times(1)).accept(eq(icon1P.getUri())); + verify(visitor, times(1)).accept(eq(icon2P.getUri())); + verify(visitor, times(1)).accept(eq(icon3P.getUri())); + verify(visitor, times(1)).accept(eq(icon4P.getUri())); + } } From 39851eeececf178cbd0a81dd53a7855057d44a54 Mon Sep 17 00:00:00 2001 From: Christophe Pinelli Date: Wed, 28 Dec 2022 04:31:11 +0000 Subject: [PATCH 44/61] [DO NOT MERGE] Backport BAL restrictions from T to S, this blocks apps from using Alarm Manager to bypass BAL restrictions. Test: atest-src BackgroundActivityLaunchTest Bug: 195756028 Change-Id: I33112ff59d913d8a7244289fe1a43512844e902a (cherry picked from commit 7a41e2fbc983ce0083b288e9489288de60dc8d8b) Merged-In: I33112ff59d913d8a7244289fe1a43512844e902a --- .../server/alarm/AlarmManagerService.java | 34 +++++++- core/java/android/app/ActivityOptions.java | 10 +-- core/java/android/app/BroadcastOptions.java | 25 +++++- core/java/android/app/ComponentOptions.java | 83 +++++++++++++++++++ .../server/am/PendingIntentRecord.java | 22 ++++- .../android/server/wm/ActivityStarter.java | 24 +++--- .../server/wm/ActivityTaskManagerService.java | 2 +- .../com/android/server/wm/AppTaskImpl.java | 2 +- .../server/alarm/AlarmManagerServiceTest.java | 59 +++++++++++-- 9 files changed, 230 insertions(+), 31 deletions(-) create mode 100644 core/java/android/app/ComponentOptions.java diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 856fbe71e31f..8f2932c96a23 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -56,6 +56,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -323,6 +324,8 @@ interface Stats { private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser = new SparseBooleanArray(); private boolean mNextAlarmClockMayChange; + ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic(); + BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic(); @GuardedBy("mLock") private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true; @@ -1651,6 +1654,11 @@ public void dumpDebug(ProtoOutputStream proto, long fieldId) { @Override public void onStart() { mInjector.init(); + mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false); + mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false); + mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false); + mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); + mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false); mMetricsHelper = new MetricsHelper(getContext(), mLock); mListenerDeathRecipient = new IBinder.DeathRecipient() { @@ -4404,6 +4412,14 @@ private static int getAlarmAttributionUid(Alarm alarm) { return alarm.creatorUid; } + private Bundle getAlarmOperationBundle(Alarm alarm) { + if (alarm.mIdleOptions != null) { + return alarm.mIdleOptions; + } else if (alarm.operation.isActivity()) { + return mActivityOptsRestrictBal.toBundle(); + } + return mBroadcastOptsRestrictBal.toBundle(); + } @VisibleForTesting class AlarmHandler extends Handler { @@ -4452,13 +4468,24 @@ public void handleMessage(Message msg) { } } } else { - removeImpl(alarm.operation, null); + try { + // Disallow AlarmManager to start random background activity. + final Bundle bundle = getAlarmOperationBundle(alarm); + alarm.operation.send(/* context */ null, /* code */ 0, /* intent */ + null, /* onFinished */ null, /* handler */ + null, /* requiredPermission */ null, bundle); + } catch (PendingIntent.CanceledException e) { + if (alarm.repeatInterval > 0) { + // This IntentSender is no longer valid, but this + // is a repeating alarm, so toss the hoser. + removeImpl(alarm.operation, null); + } + } } decrementAlarmCount(alarm.uid, 1); } break; } - case SEND_NEXT_ALARM_CLOCK_CHANGED: sendNextAlarmClockChanged(); break; @@ -5010,9 +5037,10 @@ public void deliverLocked(Alarm alarm, long nowELAPSED) { mSendCount++; try { + final Bundle bundle = getAlarmOperationBundle(alarm); alarm.operation.send(getContext(), 0, mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count), - mDeliveryTracker, mHandler, null, alarm.mIdleOptions); + mDeliveryTracker, mHandler, null, bundle); } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 479ef24fe450..6715754b41ad 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -71,7 +71,7 @@ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) * Context.startActivity(Intent, Bundle)} and related methods. */ -public class ActivityOptions { +public class ActivityOptions extends ComponentOptions { private static final String TAG = "ActivityOptions"; /** @@ -1083,13 +1083,12 @@ public boolean getLaunchTaskBehind() { } private ActivityOptions() { + super(); } /** @hide */ public ActivityOptions(Bundle opts) { - // If the remote side sent us bad parcelables, they won't get the - // results they want, which is their loss. - opts.setDefusable(true); + super(opts); mPackageName = opts.getString(KEY_PACKAGE_NAME); try { @@ -1836,8 +1835,9 @@ public void update(ActivityOptions otherOptions) { * object; you must not modify it, but can supply it to the startActivity * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (mPackageName != null) { b.putString(KEY_PACKAGE_NAME, mPackageName); } diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index bd7162c1bf3b..38c94ec20670 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -34,7 +34,7 @@ * {@hide} */ @SystemApi -public class BroadcastOptions { +public class BroadcastOptions extends ComponentOptions { private long mTemporaryAppAllowlistDuration; private @TempAllowListType int mTemporaryAppAllowlistType; private @ReasonCode int mTemporaryAppAllowlistReasonCode; @@ -108,12 +108,14 @@ public static BroadcastOptions makeBasic() { } private BroadcastOptions() { + super(); resetTemporaryAppAllowlist(); } /** @hide */ @TestApi public BroadcastOptions(@NonNull Bundle opts) { + super(opts); // Match the logic in toBundle(). if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) { mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION); @@ -190,6 +192,24 @@ private void resetTemporaryAppAllowlist() { mTemporaryAppAllowlistReason = null; } + /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + super.setPendingIntentBackgroundActivityLaunchAllowed(allowed); + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return super.isPendingIntentBackgroundActivityLaunchAllowed(); + } + /** * Return {@link #setTemporaryAppAllowlist}. * @hide @@ -308,8 +328,9 @@ public boolean allowsBackgroundActivityStarts() { * object; you must not modify it, but can supply it to the sendBroadcast * methods that take an options Bundle. */ + @Override public Bundle toBundle() { - Bundle b = new Bundle(); + Bundle b = super.toBundle(); if (isTemporaryAppAllowlistSet()) { b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration); b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType); diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java new file mode 100644 index 000000000000..86efd622cb82 --- /dev/null +++ b/core/java/android/app/ComponentOptions.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.Bundle; + +/** + * @hide + */ +public class ComponentOptions { + + /** + * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED. + * @hide + **/ + public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true; + + /** + * PendingIntent caller allows activity start even if PendingIntent creator is in background. + * This only works if the PendingIntent caller is allowed to start background activities, + * for example if it's in the foreground, or has BAL permission. + * @hide + */ + public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED = + "android.pendingIntent.backgroundActivityAllowed"; + + private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT; + + ComponentOptions() { + } + + ComponentOptions(Bundle opts) { + // If the remote side sent us bad parcelables, they won't get the + // results they want, which is their loss. + opts.setDefusable(true); + setPendingIntentBackgroundActivityLaunchAllowed( + opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + PENDING_INTENT_BAL_ALLOWED_DEFAULT)); + } + + /** + * Set PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { + mPendingIntentBalAllowed = allowed; + } + + /** + * Get PendingIntent activity is allowed to be started in the background if the caller + * can start background activities. + * + * @hide + */ + public boolean isPendingIntentBackgroundActivityLaunchAllowed() { + return mPendingIntentBalAllowed; + } + + /** + * @hide + */ + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); + return bundle; + } +} diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index dc924c11f867..7e8dcd8db7b1 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -310,6 +310,25 @@ public int sendWithResult(int code, Intent intent, String resolvedType, IBinder requiredPermission, null, null, 0, 0, 0, options); } + /** + * Return true if the activity options allows PendingIntent to use caller's BAL permission. + */ + public static boolean isPendingIntentBalAllowedByCaller( + @Nullable ActivityOptions activityOptions) { + if (activityOptions == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return isPendingIntentBalAllowedByCaller(activityOptions.toBundle()); + } + + private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) { + if (options == null) { + return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT; + } + return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT); + } + public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { @@ -431,7 +450,8 @@ public int sendInner(int code, Intent intent, String resolvedType, IBinder allow // temporarily allow receivers and services to open activities from background if the // PendingIntent.send() caller was foreground at the time of sendInner() call final boolean allowTrampoline = uid != callingUid - && controller.mAtmInternal.isUidForeground(callingUid); + && controller.mAtmInternal.isUidForeground(callingUid) + && isPendingIntentBalAllowedByCaller(options); // note: we on purpose don't pass in the information about the PendingIntent's creator, // like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 436a325559e6..6ba77cd76d8b 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -989,6 +989,10 @@ private int executeRequest(Request request) { abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, callingPackage); + // Merge the two options bundles, while realCallerOptions takes precedence. + ActivityOptions checkedOptions = options != null + ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; + boolean restrictedBgActivity = false; if (!abort) { try { @@ -997,15 +1001,12 @@ private int executeRequest(Request request) { restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, request.originatingPendingIntent, request.allowBackgroundActivityStart, - intent); + intent, checkedOptions); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } - // Merge the two options bundles, while realCallerOptions takes precedence. - ActivityOptions checkedOptions = options != null - ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; if (request.allowPendingRemoteAnimationRegistryLookup) { checkedOptions = mService.getActivityStartController() .getPendingRemoteAnimationRegistry() @@ -1247,7 +1248,7 @@ private boolean isHomeApp(int uid, @Nullable String packageName) { boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart, Intent intent) { + boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) { // don't abort for the most important UIDs final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID @@ -1318,9 +1319,12 @@ boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, ? isCallingUidPersistentSystemProcess : (realCallingAppId == Process.SYSTEM_UID) || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; - if (realCallingUid != callingUid) { - // don't abort if the realCallingUid has a visible window - // TODO(b/171459802): We should check appSwitchAllowed also + + // Legacy behavior allows to use caller foreground state to bypass BAL restriction. + final boolean balAllowedByPiSender = + PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions); + + if (balAllowedByPiSender && realCallingUid != callingUid) { if (realCallingUidHasAnyVisibleWindow) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid @@ -1393,9 +1397,9 @@ boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, // If we don't have callerApp at this point, no caller was provided to startActivity(). // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. If that's the case, we retrieve the WindowProcessController for the send() - // caller, so that we can make the decision based on its state. + // caller if caller allows, so that we can make the decision based on its state. int callerAppUid = callingUid; - if (callerApp == null) { + if (callerApp == null && balAllowedByPiSender) { callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerAppUid = realCallingUid; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index b8cdc3fbeeed..e2ef48cf3731 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2141,7 +2141,7 @@ void moveTaskToFrontLocked(@Nullable IApplicationThread appThread, final ActivityStarter starter = getActivityStartController().obtainStarter( null /* intent */, "moveTaskToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1, - -1, callerApp, null, false, null)) { + -1, callerApp, null, false, null, null)) { if (!isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 3c9adfb9c4bd..ccd525b63863 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -108,7 +108,7 @@ public void moveToFront(IApplicationThread appThread, String callingPackage) { final ActivityStarter starter = mService.getActivityStartController().obtainStarter( null /* intent */, "moveToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, - callingPackage, -1, -1, callerApp, null, false, null)) { + callingPackage, -1, -1, callerApp, null, false, null, null)) { if (!mService.isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 32d9247216c4..e2ac889c8566 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -109,6 +109,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -552,13 +553,23 @@ private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener private PendingIntent getNewMockPendingIntent() { - return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE); + return getNewMockPendingIntent(false); + } + + private PendingIntent getNewMockPendingIntent(boolean isActivity) { + return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE, isActivity); } private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage) { + return getNewMockPendingIntent(creatorUid, creatorPackage, false); + } + + private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage, + boolean isActivity) { final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS); when(mockPi.getCreatorUid()).thenReturn(creatorUid); when(mockPi.getCreatorPackage()).thenReturn(creatorPackage); + when(mockPi.isActivity()).thenReturn(isActivity); return mockPi; } @@ -2801,21 +2812,53 @@ public void removeExactAlarmsOnPermissionRevoked() { anyString())); } - @Test - public void idleOptionsSentOnExpiration() throws Exception { + private void optionsSentOnExpiration(boolean isActivity, Bundle idleOptions) + throws Exception { final long triggerTime = mNowElapsedTest + 5000; - final PendingIntent alarmPi = getNewMockPendingIntent(); - final Bundle idleOptions = new Bundle(); - idleOptions.putChar("TEST_CHAR_KEY", 'x'); - idleOptions.putInt("TEST_INT_KEY", 53); + final PendingIntent alarmPi = getNewMockPendingIntent(isActivity); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID, idleOptions); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); + ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), - any(), any(Handler.class), isNull(), eq(idleOptions)); + any(), any(Handler.class), isNull(), bundleCaptor.capture()); + if (idleOptions != null) { + assertEquals(idleOptions, bundleCaptor.getValue()); + } else { + assertFalse("BAL flag needs to be false in alarm manager", + bundleCaptor.getValue().getBoolean( + ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + true)); + } + } + + @Test + public void activityIdleOptionsSentOnExpiration() throws Exception { + final Bundle idleOptions = new Bundle(); + idleOptions.putChar("TEST_CHAR_KEY", 'x'); + idleOptions.putInt("TEST_INT_KEY", 53); + optionsSentOnExpiration(true, idleOptions); + } + + @Test + public void broadcastIdleOptionsSentOnExpiration() throws Exception { + final Bundle idleOptions = new Bundle(); + idleOptions.putChar("TEST_CHAR_KEY", 'x'); + idleOptions.putInt("TEST_INT_KEY", 53); + optionsSentOnExpiration(false, idleOptions); + } + + @Test + public void emptyActivityOptionsSentOnExpiration() throws Exception { + optionsSentOnExpiration(true, null); + } + + @Test + public void emptyBroadcastOptionsSentOnExpiration() throws Exception { + optionsSentOnExpiration(false, null); } @Test From eddde88b0eb6626e6837436a14f371f3f3c81a18 Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Tue, 25 Oct 2022 22:39:52 -0700 Subject: [PATCH 45/61] DO NOT MERGE: ActivityManager#killBackgroundProcesses can kill caller's own app only unless it's a system app. Bug: 239423414 Bug: 223376078 Test: atest CtsAppTestCases:ActivityManagerTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d1c95670b248df945784b0f2830acf83b5682de3) Merged-In: Iac6baa889965b8ffecd9a43179a4c96632ad1d02 Change-Id: Iac6baa889965b8ffecd9a43179a4c96632ad1d02 --- core/java/android/app/ActivityManager.java | 3 ++ core/res/AndroidManifest.xml | 6 +++- .../server/am/ActivityManagerService.java | 32 +++++++++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9d59225f4344..a9772e033849 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3666,6 +3666,9 @@ public void restartPackage(String packageName) { * processes to reclaim memory; the system will take care of restarting * these processes in the future as needed. * + *

Third party applications can only use this API to kill their own processes. + *

+ * * @param packageName The name of the package whose processes are to * be killed. */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e02756ced72a..70e7d172c437 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2846,7 +2846,11 @@ android:protectionLevel="normal" /> = FIRST_APPLICATION_UID + && (proc == null || !proc.info.isSystemApp())) { + final String msg = "Permission Denial: killAllBackgroundProcesses() from pid=" + + callingPid + ", uid=" + callingUid + " is not allowed"; + Slog.w(TAG, msg); + // Silently return to avoid existing apps from crashing. + return; + } + final long callingId = Binder.clearCallingIdentity(); try { synchronized (this) { From 57d066c7c18b8d2d53806571eb7ab9472c5fd1b7 Mon Sep 17 00:00:00 2001 From: Austin Borger Date: Sat, 18 Mar 2023 12:56:12 -0700 Subject: [PATCH 46/61] ActivityManagerService: Allow openContentUri from vendor/system/product. Apps should not have direct access to this entry point. Check that the caller is a vendor, system, or product package. Test: Ran PoC app and CtsMediaPlayerTestCases. Bug: 236688380 (cherry picked from commit d0ba7467c2cb2815f94f6651cbb1c2f405e8e9c7) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e37820e47c383aecf9d1173a0676c27e6a59ce4f) Merged-In: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f Change-Id: I0335496d28fa5fc3bfe1fecd4be90040b0b3687f --- .../android/server/am/ActivityManagerService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d5635497f953..6ac4eee278a1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6321,7 +6321,7 @@ public void unhandledBack() { mActivityTaskManager.unhandledBack(); } - // TODO: Move to ContentProviderHelper? + // TODO: Replace this method with one that returns a bound IContentProvider. public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException { enforceNotIsolatedCaller("openContentUri"); final int userId = UserHandle.getCallingUserId(); @@ -6350,6 +6350,16 @@ public ParcelFileDescriptor openContentUri(String uriString) throws RemoteExcept Log.e(TAG, "Cannot find package for uid: " + uid); return null; } + + final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo( + androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID, + UserHandle.USER_SYSTEM); + if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt() + && !appInfo.isProduct()) { + Log.e(TAG, "openContentUri may only be used by vendor/system/product."); + return null; + } + final AttributionSource attributionSource = new AttributionSource( Binder.getCallingUid(), androidPackage.getPackageName(), null); pfd = cph.provider.openFile(attributionSource, uri, "r", null); From 94664d1aeebe9ccf81124d07162e9a38849c2fb5 Mon Sep 17 00:00:00 2001 From: Silin Huang Date: Wed, 12 Apr 2023 17:22:11 +0000 Subject: [PATCH 47/61] Do not load drawable for wallet card if the card image icon iscreated with content URI. This prevents the primary user from accessing the secondary user's photos for QAW card images. Test: manually, atest Bug: 272020068 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ff753ae693065685d85bbda6af2953905fdf434c) Merged-In: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e Change-Id: I6932c5131b3c795bac4ea9b537938e7ef4f3ea4e --- .../qs/tiles/QuickAccessWalletTile.java | 8 ++++- .../wallet/ui/WalletScreenController.java | 7 ++++- .../qs/tiles/QuickAccessWalletTileTest.java | 31 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index d9919bdac889..b5484b952c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; @@ -240,7 +241,12 @@ public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) { return; } mSelectedCard = cards.get(selectedIndex); - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); + if (cardImageIcon.getType() == TYPE_URI) { + mCardViewDrawable = null; + } else { + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index ba9b638fac99..2aafc14be551 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -318,7 +318,12 @@ static class QAWalletCardViewInfo implements WalletCardViewInfo { */ QAWalletCardViewInfo(Context context, WalletCard walletCard) { mWalletCard = walletCard; - mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + Icon cardImageIcon = mWalletCard.getCardImage(); + if (cardImageIcon.getType() == Icon.TYPE_URI) { + mCardDrawable = null; + } else { + mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + } Icon icon = mWalletCard.getCardIcon(); mIconDrawable = icon == null ? null : icon.loadDrawable(context); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 8922b43b7447..1ff7cf7b8356 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -91,8 +91,11 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private static final String CARD_ID = "card_id"; private static final String LABEL = "QAW"; + private static final String CARD_DESCRIPTION = "•••• 1234"; private static final Icon CARD_IMAGE = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) @@ -119,6 +122,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private QuickAccessWalletController mController; + @Mock + private Icon mCardImage; @Captor ArgumentCaptor mIntentCaptor; @Captor @@ -144,6 +149,8 @@ public void setUp() throws Exception { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); + when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); + when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); mTile = new QuickAccessWalletTile( mHost, @@ -418,6 +425,28 @@ public void testQueryCards_hasCards_updateSideViewDrawable() { assertNotNull(mTile.getState().sideViewCustomDrawable); } + @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); @@ -465,6 +494,6 @@ private void setUpWalletCard(boolean hasCard) { private WalletCard createWalletCard(Context context) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); - return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build(); + return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); } } From d9342966358496c5f7a091da24ddda6eff4634d9 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 14:55:28 +0000 Subject: [PATCH 48/61] Verify URI permissions for notification shortcutIcon. Bug: 277593270 Test: atest NotificationManagerServiceTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:beb185c5cd60edc68f4ef386c4407eba9c02c698) Merged-In: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 Change-Id: Iaf2a9a82f18e018e60e6cdc020da6ebf7267e8b1 --- core/java/android/app/Notification.java | 2 + .../NotificationManagerServiceTest.java | 87 +++++++++++++++---- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b4670b7cb0b4..59364ffcfc62 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2846,6 +2846,8 @@ public void visitUris(@NonNull Consumer visitor) { } } } + + visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON)); } if (isStyle(CallStyle.class) & extras != null) { 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 5713f689e17a..230e89c1e7f0 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4447,6 +4447,8 @@ public void updateUriPermissions_posterDoesNotOwnUri() throws Exception { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon"); final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); @@ -4480,7 +4482,8 @@ public void testVisitUris() throws Exception { Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setSmallIcon(smallIcon) + .setLargeIcon(largeIcon) .addExtras(extras) .build(); @@ -4488,6 +4491,8 @@ public void testVisitUris() throws Exception { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(smallIcon.getUri())); + verify(visitor, times(1)).accept(eq(largeIcon.getUri())); verify(visitor, times(1)).accept(eq(personIcon1.getUri())); verify(visitor, times(1)).accept(eq(personIcon2.getUri())); verify(visitor, times(1)).accept(eq(personIcon3.getUri())); @@ -4495,6 +4500,68 @@ public void testVisitUris() throws Exception { verify(visitor, times(1)).accept(eq(historyUri2)); } + @Test + public void testVisitUris_audioContentsString() throws Exception { + final Uri audioContents = Uri.parse("content://com.example/audio"); + + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); + + Notification n = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addExtras(extras) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(audioContents)); + } + + @Test + public void testVisitUris_messagingStyle() { + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person 1") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("Messaging Person 2") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("Messaging Person 3") + .setIcon(personIcon3) + .build(); + Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut"); + + Notification.Builder builder = new Notification.Builder(mContext, "a") + .setCategory(Notification.CATEGORY_MESSAGE) + .setContentTitle("new message!") + .setContentText("Conversation Notification") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message( + "Marco?", System.currentTimeMillis(), person2); + Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message( + "Polo!", System.currentTimeMillis(), person3); + Notification.MessagingStyle style = new Notification.MessagingStyle(person1) + .addMessage(message1) + .addMessage(message2) + .setShortcutIcon(shortcutIcon); + builder.setStyle(style); + Notification n = builder.build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(shortcutIcon.getUri())); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + } + @Test public void testVisitUris_callStyle() { Icon personIcon = Icon.createWithContentUri("content://media/person"); @@ -4518,24 +4585,6 @@ public void testVisitUris_callStyle() { verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); } - @Test - public void testVisitUris_audioContentsString() throws Exception { - final Uri audioContents = Uri.parse("content://com.example/audio"); - - Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); - - Notification n = new Notification.Builder(mContext, "a") - .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addExtras(extras) - .build(); - - Consumer visitor = (Consumer) spy(Consumer.class); - n.visitUris(visitor); - verify(visitor, times(1)).accept(eq(audioContents)); - } - @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); From 2b2b77879d37030a800804bbb989012dc3ab825b Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Wed, 5 Apr 2023 15:15:41 +0000 Subject: [PATCH 49/61] Ensure policy has no absurdly long strings The following APIs now enforce limits and throw IllegalArgumentException when limits are violated: * DPM.setTrustAgentConfiguration() limits agent packgage name, component name, and strings within configuration bundle. * DPM.setPermittedAccessibilityServices() limits package names. * DPM.setPermittedInputMethods() limits package names. * DPM.setAccountManagementDisabled() limits account name. * DPM.setLockTaskPackages() limits package names. * DPM.setAffiliationIds() limits id. * DPM.transferOwnership() limits strings inside the bundle. Package names are limited at 223, because they become directory names and it is a filesystem restriction, see FrameworkParsingPackageUtils. All other strings are limited at 65535, because longer ones break binary XML serializer. The following APIs silently truncate strings that are long beyond reason: * DPM.setShortSupportMessage() truncates message at 200. * DPM.setLongSupportMessage() truncates message at 20000. * DPM.setOrganizationName() truncates org name at 200. Bug: 260729089 Test: atest com.android.server.devicepolicy (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:12c201509e911f4dddabf371bd22c93e097e5d99) Merged-In: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 Change-Id: Idcf54e408722f164d16bf2f24a00cd1f5b626d23 --- .../app/admin/DevicePolicyManager.java | 3 +- .../DevicePolicyManagerService.java | 93 ++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 117e3f6f143d..ea215a44ab59 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -11411,7 +11411,8 @@ public CharSequence getShortSupportMessage(@NonNull ComponentName admin) { /** * Called by a device admin to set the long support message. This will be displayed to the user - * in the device administators settings screen. + * in the device administrators settings screen. If the message is longer than 20000 characters + * it may be truncated. *

* If the long support message needs to be localized, it is the responsibility of the * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9e375a0ba265..061c0f706a77 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -361,6 +361,7 @@ import java.security.cert.X509Certificate; import java.text.DateFormat; import java.time.LocalDate; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -372,6 +373,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -400,6 +402,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572; + // Binary XML serializer doesn't support longer strings + private static final int MAX_POLICY_STRING_LENGTH = 65535; + // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names. + private static final int MAX_PACKAGE_NAME_LENGTH = 223; + + private static final int MAX_LONG_SUPPORT_MESSAGE_LENGTH = 20000; + private static final int MAX_SHORT_SUPPORT_MESSAGE_LENGTH = 200; + private static final int MAX_ORG_NAME_LENGTH = 200; + private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms @@ -9959,6 +9970,12 @@ public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, } Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); + enforceMaxPackageNameLength(agent.getPackageName()); + final String agentAsString = agent.flattenToString(); + enforceMaxStringLength(agentAsString, "agent name"); + if (args != null) { + enforceMaxStringLength(args, "args"); + } final int userHandle = UserHandle.getCallingUserId(); synchronized (getLockObject()) { ActiveAdmin ap = getActiveAdminForCallerLocked(admin, @@ -10197,6 +10214,10 @@ public boolean setPermittedAccessibilityServices(ComponentName who, List package final CallerIdentity caller = getCallerIdentity(who); if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + int userId = caller.getUserId(); final List enabledServices; long id = mInjector.binderClearCallingIdentity(); @@ -10363,6 +10384,10 @@ public boolean setPermittedInputMethods(ComponentName who, List packageList, } if (packageList != null) { + for (String pkg : (List) packageList) { + enforceMaxPackageNameLength(pkg); + } + List enabledImes = mInjector.binderWithCleanCallingIdentity(() -> InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId)); if (enabledImes != null) { @@ -11682,6 +11707,8 @@ public void setAccountManagementDisabled(ComponentName who, String accountType, return; } Objects.requireNonNull(who, "ComponentName is null"); + enforceMaxStringLength(accountType, "account type"); + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { /* @@ -12100,6 +12127,10 @@ public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packages, "packages is null"); + for (String pkg : packages) { + enforceMaxPackageNameLength(pkg); + } + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { @@ -14150,6 +14181,8 @@ public void setShortSupportMessage(@NonNull ComponentName who, CharSequence mess return; } Objects.requireNonNull(who, "ComponentName is null"); + message = truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); + final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { ActiveAdmin admin = getActiveAdminForUidLocked(who, caller.getUid()); @@ -14182,6 +14215,9 @@ public void setLongSupportMessage(@NonNull ComponentName who, CharSequence messa if (!mHasFeature) { return; } + + message = truncateIfLonger(message, MAX_LONG_SUPPORT_MESSAGE_LENGTH); + Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); synchronized (getLockObject()) { @@ -14331,6 +14367,8 @@ public void setOrganizationName(@NonNull ComponentName who, CharSequence text) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); + text = truncateIfLonger(text, MAX_ORG_NAME_LENGTH); + synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); if (!TextUtils.equals(admin.organizationName, text)) { @@ -14580,9 +14618,8 @@ public void setAffiliationIds(ComponentName admin, List ids) { throw new IllegalArgumentException("ids must not be null"); } for (String id : ids) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("ids must not contain empty string"); - } + Preconditions.checkArgument(!TextUtils.isEmpty(id), "ids must not have empty string"); + enforceMaxStringLength(id, "affiliation id"); } final Set affiliationIds = new ArraySet<>(ids); @@ -15865,6 +15902,9 @@ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentNa "Provided administrator and target are the same object."); Preconditions.checkArgument(!admin.getPackageName().equals(target.getPackageName()), "Provided administrator and target have the same package name."); + if (bundle != null) { + enforceMaxStringLength(bundle, "bundle"); + } final CallerIdentity caller = getCallerIdentity(admin); Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); @@ -17974,4 +18014,51 @@ public boolean canUsbDataSignalingBeDisabled() { && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 ); } + + /** + * Truncates char sequence to maximum length, nulls are ignored. + */ + private static CharSequence truncateIfLonger(CharSequence input, int maxLength) { + return input == null || input.length() <= maxLength + ? input + : input.subSequence(0, maxLength); + } + + /** + * Throw if string argument is too long to be serialized. + */ + private static void enforceMaxStringLength(String str, String argName) { + Preconditions.checkArgument( + str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long"); + } + + private static void enforceMaxPackageNameLength(String pkg) { + Preconditions.checkArgument( + pkg.length() <= MAX_PACKAGE_NAME_LENGTH, "Package name too long"); + } + + /** + * Throw if persistable bundle contains any string that we can't serialize. + */ + private static void enforceMaxStringLength(PersistableBundle bundle, String argName) { + // Persistable bundles can have other persistable bundles as values, traverse with a queue. + Queue queue = new ArrayDeque<>(); + queue.add(bundle); + while (!queue.isEmpty()) { + PersistableBundle current = queue.remove(); + for (String key : current.keySet()) { + enforceMaxStringLength(key, "key in " + argName); + Object value = current.get(key); + if (value instanceof String) { + enforceMaxStringLength((String) value, "string value in " + argName); + } else if (value instanceof String[]) { + for (String str : (String[]) value) { + enforceMaxStringLength(str, "string value in " + argName); + } + } else if (value instanceof PersistableBundle) { + queue.add((PersistableBundle) value); + } + } + } + } } From 1bae3b1b235b50b72650f34fe3c137dd83de1401 Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 8 May 2023 16:33:12 +0000 Subject: [PATCH 50/61] On device lockdown, always show the keyguard Manual test steps: 1. Enable app pinning and disable "Ask for PIN before unpinning" setting 2. Pin an app (ie: Settings) 3. Lockdown from the power menu Observe: user is brought to the keyguard, primary auth is required to enter the device. After entering credential, the device is still in app pinning mode. Test: atest KeyguardViewMediatorTest Test: manual steps outlined above Bug: 218495634 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b23c2d5fb6630ea0da503b937f62880594b13e94) Merged-In: I9a7c5e1acadabd4484e58573331f98dba895f2a2 Change-Id: I9a7c5e1acadabd4484e58573331f98dba895f2a2 Change-Id: Ia967920c8b3f2388d7a1d4ce7a717525b2680923 --- .../systemui/keyguard/KeyguardViewMediator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index dc27dcac4692..82b68a043f1b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -691,6 +691,13 @@ public void onTrustChanged(int userId) { } } } + + @Override + public void onStrongAuthStateChanged(int userId) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + doKeyguardLocked(null); + } + } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -1578,7 +1585,8 @@ private void doKeyguardLocked(Bundle options) { } // if another app is disabling us, don't show - if (!mExternallyEnabled) { + if (!mExternallyEnabled + && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; From 45e38e6875942125984171d7cf35e5582672b976 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Wed, 17 May 2023 01:30:20 -0700 Subject: [PATCH 51/61] Preserve flags for non-runtime permissions upon package update. PermissionManagerServiceImpl.restorePermissionState() creates a new UID permission state for non-shared-UID packages that have been updated (i.e. replaced), however the existing logic for non-runtime permission never carried over the flags from the old state. This wasn't an issue for much older platforms because permission flags weren't used for non-runtime permissions, however since we are starting to use them for role protected permissions (ROLE_GRANTED) and app op permissions (USER_SET), we do need to preserver the permission flags. This change merges the logic for granting and revoking a non-runtime permission in restorePermissionState() into a single if branch, and appends the logic to copy the flag from the old state in that branch. Bug: 283006437 Test: PermissionFlagsTest#nonRuntimePermissionFlagsPreservedAfterReinstall (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0e1ebd84e27f5d4fa8bc6577705293251bcbac4f) Merged-In: Iea3c66710e7d28c6fc730b1939da64f1172b08db Change-Id: Iea3c66710e7d28c6fc730b1939da64f1172b08db --- .../permission/PermissionManagerService.java | 88 +++++++++++-------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index bd3a16c8087e..1d6d9974e3ba 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2899,29 +2899,55 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, + pkg.getPackageName()); } - if ((bp.isNormal() && shouldGrantNormalPermission) - || (bp.isSignature() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantSignaturePermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName)))) - || (bp.isInternal() - && (!bp.isPrivileged() || CollectionUtils.contains( - isPrivilegedPermissionAllowlisted, permName)) - && (CollectionUtils.contains(shouldGrantInternalPermission, - permName) - || (((bp.isPrivileged() && CollectionUtils.contains( - shouldGrantPrivilegedPermissionIfWasGranted, - permName)) || bp.isDevelopment() || bp.isRole()) - && origState.isPermissionGranted(permName))))) { - // Grant an install permission. - if (uidState.grantPermission(bp)) { - changedInstallPermission = true; + if (bp.isNormal() || bp.isSignature() || bp.isInternal()) { + if ((bp.isNormal() && shouldGrantNormalPermission) + || (bp.isSignature() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantSignaturePermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() + || bp.isRole()) + && origState.isPermissionGranted( + permName)))) + || (bp.isInternal() + && (!bp.isPrivileged() || CollectionUtils.contains( + isPrivilegedPermissionAllowlisted, permName)) + && (CollectionUtils.contains(shouldGrantInternalPermission, + permName) + || (((bp.isPrivileged() && CollectionUtils.contains( + shouldGrantPrivilegedPermissionIfWasGranted, + permName)) || bp.isDevelopment() + || bp.isRole()) + && origState.isPermissionGranted( + permName))))) { + // Grant an install permission. + if (uidState.grantPermission(bp)) { + changedInstallPermission = true; + } + } else { + if (DEBUG_PERMISSIONS) { + boolean wasGranted = uidState.isPermissionGranted(bp.getName()); + if (wasGranted || bp.isAppOp()) { + Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") + + " permission " + perm + + " from package " + friendlyName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, + ps)) + + ")"); + } + } + if (uidState.revokePermission(bp)) { + changedInstallPermission = true; + } } + PermissionState origPermState = origState.getPermissionState(perm); + int flags = origPermState != null ? origPermState.getFlags() : 0; + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags); } else if (bp.isRuntime()) { boolean hardRestricted = bp.isHardRestricted(); boolean softRestricted = bp.isSoftRestricted(); @@ -3045,22 +3071,8 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags); } else { - if (DEBUG_PERMISSIONS) { - boolean wasGranted = uidState.isPermissionGranted(bp.getName()); - if (wasGranted || bp.isAppOp()) { - Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting") - + " permission " + perm - + " from package " + friendlyName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, - ps)) - + ")"); - } - } - if (uidState.removePermissionState(bp.getName())) { - changedInstallPermission = true; - } + Slog.wtf(LOG_TAG, "Unknown permission protection " + bp.getProtection() + + " for permission " + bp.getName()); } } From 5a1f60377bdd3d99c75ebfbc8dce5dc396a4454e Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Mon, 15 May 2023 16:15:55 +0000 Subject: [PATCH 52/61] Check URIs in notification public version. Bug: 276294099 Test: atest NotificationManagerServiceTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:67cd169d073486c7c047b80ab83843cdee69bf53) Merged-In: I670198b213abb2cb29a9865eb9d1e897700508b4 Change-Id: I670198b213abb2cb29a9865eb9d1e897700508b4 --- core/java/android/app/Notification.java | 4 ++++ .../NotificationManagerServiceTest.java | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 59364ffcfc62..1f59c8eac70f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2761,6 +2761,10 @@ private static void visitIconUri(@NonNull Consumer visitor, @Nullable Icon * @hide */ public void visitUris(@NonNull Consumer visitor) { + if (publicVersion != null) { + publicVersion.visitUris(visitor); + } + visitor.accept(sound); if (tickerView != null) tickerView.visitUris(visitor); 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 230e89c1e7f0..672500b36ee4 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4500,6 +4500,26 @@ public void testVisitUris() throws Exception { verify(visitor, times(1)).accept(eq(historyUri2)); } + @Test + public void testVisitUris_publicVersion() throws Exception { + final Icon smallIconPublic = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIconPrivate = Icon.createWithContentUri("content://media/large/icon"); + + Notification publicVersion = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(smallIconPublic) + .build(); + Notification n = new Notification.Builder(mContext, "a") + .setLargeIcon(largeIconPrivate) + .setPublicVersion(publicVersion) + .build(); + + Consumer visitor = (Consumer) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(smallIconPublic.getUri())); + verify(visitor, times(1)).accept(eq(largeIconPrivate.getUri())); + } + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); From 29a2885ddea7401ae2c0a251b9062624f4568d6d Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 12 May 2023 15:41:09 +0000 Subject: [PATCH 53/61] Implement visitUris for RemoteViews ViewGroupActionAdd. This is to prevent a vulnerability where notifications can show resources belonging to other users, since the URI in the nested views was not being checked. Bug: 277740082 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:850fd984e5f346645b5a941ed7307387c7e4c4de) Merged-In: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 Change-Id: I5c71f0bad0a6f6361eb5ceffe8d1e47e936d78f8 --- core/java/android/widget/RemoteViews.java | 5 ++++ .../src/android/widget/RemoteViewsTest.java | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index fe6eb32f0158..0c89a679101a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2560,6 +2560,11 @@ public boolean prefersAsyncApply() { public int getActionTag() { return VIEW_GROUP_ACTION_ADD_TAG; } + + @Override + public final void visitUris(@NonNull Consumer visitor) { + mNestedViews.visitUris(visitor); + } } /** diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 6cdf72071194..f0f9056cc5b7 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -528,6 +528,30 @@ public void visitUris() { verify(visitor, times(1)).accept(eq(icon4.getUri())); } + @Test + public void visitUris_nestedViews() { + final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); + + final RemoteViews inner = new RemoteViews(mPackage, 33); + final Uri imageUriI = Uri.parse("content://inner/image"); + final Icon icon1 = Icon.createWithContentUri("content://inner/icon1"); + final Icon icon2 = Icon.createWithContentUri("content://inner/icon2"); + final Icon icon3 = Icon.createWithContentUri("content://inner/icon3"); + final Icon icon4 = Icon.createWithContentUri("content://inner/icon4"); + inner.setImageViewUri(R.id.image, imageUriI); + inner.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4); + + outer.addView(R.id.layout, inner); + + Consumer visitor = (Consumer) spy(Consumer.class); + outer.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriI)); + verify(visitor, times(1)).accept(eq(icon1.getUri())); + verify(visitor, times(1)).accept(eq(icon2.getUri())); + verify(visitor, times(1)).accept(eq(icon3.getUri())); + verify(visitor, times(1)).accept(eq(icon4.getUri())); + } + @Test public void visitUris_separateOrientation() { final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test); From f0d365532898a400feb236bcf2c016b5b1618145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Budnik?= Date: Tue, 4 Apr 2023 17:58:26 +0000 Subject: [PATCH 54/61] Validate ComponentName for MediaButtonBroadcastReceiver This is a security fix for b/270049379. Bug: 270049379 Test: atest CtsMediaMiscTestCases (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c573c83a2aa36ca022302f675d705518dd723a3c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ba546a306217389a8ff9e5e948612651fd496081) Merged-In: I05626f7abf1efef86c9e01ee3f077d7177d7f662 Change-Id: I05626f7abf1efef86c9e01ee3f077d7177d7f662 --- .../android/media/session/MediaSession.java | 8 +++-- .../server/media/MediaSessionRecord.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 24118b086c24..a14999df666c 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -297,9 +297,11 @@ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { * class that should receive media buttons. This allows restarting playback after the session * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} * intent will be sent to the broadcast receiver. - *

- * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package - * as the context that was given when creating {@link MediaSession}. + * + *

Note: The given {@link android.content.BroadcastReceiver} should belong to the same + * package as the context that was given when creating {@link MediaSession}. + * + *

Calls with invalid or non-existent receivers will be ignored. * * @param broadcastReceiver the component name of the BroadcastReceiver class */ diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 3a427ddeb739..31ad43b5a409 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -16,12 +16,17 @@ package com.android.server.media; +import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; @@ -52,6 +57,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; +import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -879,6 +885,22 @@ public void run() { } }; + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + private static boolean componentNameExists( + @NonNull ComponentName componentName, @NonNull Context context, int userId) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.setComponent(componentName); + + UserHandle userHandle = UserHandle.of(userId); + PackageManager pm = context.getPackageManager(); + + List resolveInfos = + pm.queryBroadcastReceiversAsUser( + mediaButtonIntent, /* flags */ 0, userHandle); + return !resolveInfos.isEmpty(); + } + private final class SessionStub extends ISession.Stub { @Override public void destroySession() throws RemoteException { @@ -949,6 +971,7 @@ public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { } @Override + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { final long token = Binder.clearCallingIdentity(); try { @@ -964,6 +987,16 @@ public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws Remot != 0) { return; } + + if (!componentNameExists(receiver, mContext, mUserId)) { + Log.w( + TAG, + "setMediaButtonBroadcastReceiver(): " + + "Ignoring invalid component name=" + + receiver); + return; + } + mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { From 0b922fed7777d7b5f366b3c204e0927ef87d75fd Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Tue, 23 May 2023 16:26:41 +0000 Subject: [PATCH 55/61] Check URIs in sized remote views. Bug: 277741109 Test: atest RemoteViewsTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ae0d45137b0f8ea49a085bbce4d39f901685c4a5) Merged-In: Iceb33606da3a49b9638ab21aeae17a168c1b411a Change-Id: Iceb33606da3a49b9638ab21aeae17a168c1b411a --- core/java/android/widget/RemoteViews.java | 5 +++ .../src/android/widget/RemoteViewsTest.java | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0c89a679101a..81061343844f 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -709,6 +709,11 @@ public void visitUris(@NonNull Consumer visitor) { mActions.get(i).visitUris(visitor); } } + if (mSizedRemoteViews != null) { + for (int i = 0; i < mSizedRemoteViews.size(); i++) { + mSizedRemoteViews.get(i).visitUris(visitor); + } + } if (mLandscape != null) { mLandscape.visitUris(visitor); } diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index f0f9056cc5b7..e33b7e69caa4 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -38,6 +38,7 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Parcel; +import android.util.SizeF; import android.view.View; import android.view.ViewGroup; @@ -55,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; @@ -587,4 +589,43 @@ public void visitUris_separateOrientation() { verify(visitor, times(1)).accept(eq(icon3P.getUri())); verify(visitor, times(1)).accept(eq(icon4P.getUri())); } + + @Test + public void visitUris_sizedViews() { + final RemoteViews large = new RemoteViews(mPackage, R.layout.remote_views_test); + final Uri imageUriL = Uri.parse("content://large/image"); + final Icon icon1L = Icon.createWithContentUri("content://large/icon1"); + final Icon icon2L = Icon.createWithContentUri("content://large/icon2"); + final Icon icon3L = Icon.createWithContentUri("content://large/icon3"); + final Icon icon4L = Icon.createWithContentUri("content://large/icon4"); + large.setImageViewUri(R.id.image, imageUriL); + large.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L); + + final RemoteViews small = new RemoteViews(mPackage, 33); + final Uri imageUriS = Uri.parse("content://small/image"); + final Icon icon1S = Icon.createWithContentUri("content://small/icon1"); + final Icon icon2S = Icon.createWithContentUri("content://small/icon2"); + final Icon icon3S = Icon.createWithContentUri("content://small/icon3"); + final Icon icon4S = Icon.createWithContentUri("content://small/icon4"); + small.setImageViewUri(R.id.image, imageUriS); + small.setTextViewCompoundDrawables(R.id.text, icon1S, icon2S, icon3S, icon4S); + + HashMap sizedViews = new HashMap<>(); + sizedViews.put(new SizeF(300, 300), large); + sizedViews.put(new SizeF(100, 100), small); + RemoteViews views = new RemoteViews(sizedViews); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(imageUriL)); + verify(visitor, times(1)).accept(eq(icon1L.getUri())); + verify(visitor, times(1)).accept(eq(icon2L.getUri())); + verify(visitor, times(1)).accept(eq(icon3L.getUri())); + verify(visitor, times(1)).accept(eq(icon4L.getUri())); + verify(visitor, times(1)).accept(eq(imageUriS)); + verify(visitor, times(1)).accept(eq(icon1S.getUri())); + verify(visitor, times(1)).accept(eq(icon2S.getUri())); + verify(visitor, times(1)).accept(eq(icon3S.getUri())); + verify(visitor, times(1)).accept(eq(icon4S.getUri())); + } } From 6de8b774912bcd019b526c45fc9bab27524acc8c Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Mon, 22 May 2023 10:21:02 +0200 Subject: [PATCH 56/61] Fix PrivacyChip not visible issue Bug: 281807669 Test: Manual, i.e. posting the following sequence of events (within few milliseconds) to the scheduler and observe the behaviour with and without the fix: Mic in use -> Mic not in use -> Mic in use (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a45e1d045770eaabfdbf0e1212c9eb84caf1d565) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:20ea049a4a52dbc8d4e5ed957a2b6b9aa02a2f34) Merged-In: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 Change-Id: I9851e6ed4cb956d0459ef56251eb0ef3210764b8 --- .../src/com/android/systemui/statusbar/events/StatusEvent.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt index d4d84c138b20..f610101631dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -86,9 +86,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { - return other is PrivacyEvent && - (other.privacyItems != privacyItems || - other.contentDescription != contentDescription) + return other is PrivacyEvent } override fun updateFromEvent(other: StatusEvent?) { From 8ab4f757aec4d5eb803f66910a724e2620b45d6d Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 25 May 2023 11:43:43 +0000 Subject: [PATCH 57/61] Visit URIs in themed remoteviews icons. Bug: 281018094 Test: atest RemoteViewsTest NotificationVisitUrisTest (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:634a69b7700017eac534f3f58cdcc2572f3cc659) Merged-In: I2014bf21cf90267f7f1b3f370bf00ab7001b064e Change-Id: I2014bf21cf90267f7f1b3f370bf00ab7001b064e --- core/java/android/widget/RemoteViews.java | 10 +++++++++- .../src/android/widget/RemoteViewsTest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 81061343844f..3b5ba30fe922 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1808,7 +1808,7 @@ public final boolean prefersAsyncApply() { } @Override - public final void visitUris(@NonNull Consumer visitor) { + public void visitUris(@NonNull Consumer visitor) { switch (this.type) { case URI: final Uri uri = (Uri) getParameterValue(null); @@ -2271,6 +2271,14 @@ protected Object getParameterValue(@Nullable View view) throws ActionException { public int getActionTag() { return NIGHT_MODE_REFLECTION_ACTION_TAG; } + + @Override + public void visitUris(@NonNull Consumer visitor) { + if (this.type == ICON) { + visitIconUri((Icon) mDarkValue, visitor); + visitIconUri((Icon) mLightValue, visitor); + } + } } /** diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index e33b7e69caa4..7925136dd615 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -530,6 +530,19 @@ public void visitUris() { verify(visitor, times(1)).accept(eq(icon4.getUri())); } + @Test + public void visitUris_themedIcons() { + RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); + final Icon iconLight = Icon.createWithContentUri("content://light/icon"); + final Icon iconDark = Icon.createWithContentUri("content://dark/icon"); + views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark); + + Consumer visitor = (Consumer) spy(Consumer.class); + views.visitUris(visitor); + verify(visitor, times(1)).accept(eq(iconLight.getUri())); + verify(visitor, times(1)).accept(eq(iconDark.getUri())); + } + @Test public void visitUris_nestedViews() { final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test); From d82bcdfb1268e0cc87aa9ed25036e206584d0039 Mon Sep 17 00:00:00 2001 From: Treehugger Robot Date: Fri, 2 Jun 2023 20:27:03 +0000 Subject: [PATCH 58/61] Merge "Use Settings.System.getIntForUser instead of getInt to make sure user specific settings are used" into rvc-dev am: d198f5165c am: 886d492c8c Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23475765 Signed-off-by: Automerger Merge Worker (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f37a92b8c8c98ca40f858782fe3720362565c16c) Merged-In: Idda8cdb4c853b6046ba19d35eeea2a1a6ee73541 Change-Id: Idda8cdb4c853b6046ba19d35eeea2a1a6ee73541 --- .../systemui/keyguard/KeyguardViewMediator.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 82b68a043f1b..4931cbbede18 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1160,9 +1160,9 @@ private long getLockTimeout(int userId) { final ContentResolver cr = mContext.getContentResolver(); // From SecuritySettings - final long lockAfterTimeout = Settings.Secure.getInt(cr, + final long lockAfterTimeout = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId); // From DevicePolicyAdmin final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() @@ -1174,8 +1174,8 @@ private long getLockTimeout(int userId) { timeout = lockAfterTimeout; } else { // From DisplaySettings - long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, - KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId); // policy in effect. Make sure we don't go beyond policy limit. displayTimeout = Math.max(displayTimeout, 0); // ignore negative values @@ -2087,7 +2087,10 @@ private void playSounds(boolean locked) { private void playSound(int soundId) { if (soundId == 0) return; final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr, + Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1, + KeyguardUpdateMonitor.getCurrentUser()); + if (lockscreenSoundsEnabled == 1) { mLockSounds.stop(mLockSoundStreamId); // Init mAudioManager From 9765e7ac6912cf32a267a6c1b53e4e5324ae5ee5 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Fri, 19 May 2023 15:52:00 -0700 Subject: [PATCH 59/61] Remove unnecessary padding code Bug: 213170822 Remove the code that CursorWindow::writeToParcel() uses to ensure slot data is 4-byte aligned. Because mAllocOffset and mSlotsOffset are already 4-byte aligned, the alignment step here is unnecessary. CursorWindow::spaceInUse() returns the total space used. The tests verify that the total space used is always a multiple of 4 bytes. Test: atest * libandroidfw_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5d4afa0986cbc440f458b4b8db05fd176ef3e6d2) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:548b0a183859fb023dee7ecd7d9f05bf7fed00f8) Merged-In: I720699093d5c5a584283e5b76851938f449ffa21 Change-Id: I720699093d5c5a584283e5b76851938f449ffa21 --- libs/androidfw/CursorWindow.cpp | 10 +++--- .../include/androidfw/CursorWindow.h | 3 ++ libs/androidfw/tests/CursorWindow_test.cpp | 31 +++++++++++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 3527eeead1d5..2a6dc7b95c07 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -108,7 +108,7 @@ status_t CursorWindow::maybeInflate() { { // Migrate existing contents into new ashmem region - uint32_t slotsSize = mSize - mSlotsOffset; + uint32_t slotsSize = sizeOfSlots(); uint32_t newSlotsOffset = mInflatedSize - slotsSize; memcpy(static_cast(newData), static_cast(mData), mAllocOffset); @@ -216,11 +216,9 @@ status_t CursorWindow::writeToParcel(Parcel* parcel) { if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail; } else { // Since we know we're going to be read-only on the remote side, - // we can compact ourselves on the wire, with just enough padding - // to ensure our slots stay aligned - size_t slotsSize = mSize - mSlotsOffset; - size_t compactedSize = mAllocOffset + slotsSize; - compactedSize = (compactedSize + 3) & ~3; + // we can compact ourselves on the wire. + size_t slotsSize = sizeOfSlots(); + size_t compactedSize = sizeInUse(); if (parcel->writeUint32(compactedSize)) goto fail; if (parcel->writeBool(false)) goto fail; void* dest = parcel->writeInplace(compactedSize); diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h index 6e55a9a0eb8b..9ec026a19c4c 100644 --- a/libs/androidfw/include/androidfw/CursorWindow.h +++ b/libs/androidfw/include/androidfw/CursorWindow.h @@ -90,6 +90,9 @@ class CursorWindow { inline uint32_t getNumRows() { return mNumRows; } inline uint32_t getNumColumns() { return mNumColumns; } + inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; } + inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); } + status_t clear(); status_t setNumColumns(uint32_t numColumns); diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp index 15be80c48192..9ac427b66cb3 100644 --- a/libs/androidfw/tests/CursorWindow_test.cpp +++ b/libs/androidfw/tests/CursorWindow_test.cpp @@ -20,9 +20,16 @@ #include "TestHelpers.h" +// Verify that the memory in use is a multiple of 4 bytes +#define ASSERT_ALIGNED(w) \ + ASSERT_EQ(((w)->sizeInUse() & 3), 0); \ + ASSERT_EQ(((w)->freeSpace() & 3), 0); \ + ASSERT_EQ(((w)->sizeOfSlots() & 3), 0) + #define CREATE_WINDOW_1K \ CursorWindow* w; \ - CursorWindow::create(String8("test"), 1 << 10, &w); + CursorWindow::create(String8("test"), 1 << 10, &w); \ + ASSERT_ALIGNED(w); #define CREATE_WINDOW_1K_3X3 \ CursorWindow* w; \ @@ -30,11 +37,13 @@ ASSERT_EQ(w->setNumColumns(3), OK); \ ASSERT_EQ(w->allocRow(), OK); \ ASSERT_EQ(w->allocRow(), OK); \ - ASSERT_EQ(w->allocRow(), OK); + ASSERT_EQ(w->allocRow(), OK); \ + ASSERT_ALIGNED(w); #define CREATE_WINDOW_2M \ CursorWindow* w; \ - CursorWindow::create(String8("test"), 1 << 21, &w); + CursorWindow::create(String8("test"), 1 << 21, &w); \ + ASSERT_ALIGNED(w); static constexpr const size_t kHalfInlineSize = 8192; static constexpr const size_t kGiantSize = 1048576; @@ -48,6 +57,7 @@ TEST(CursorWindowTest, Empty) { ASSERT_EQ(w->getNumColumns(), 0); ASSERT_EQ(w->size(), 1 << 10); ASSERT_EQ(w->freeSpace(), 1 << 10); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, SetNumColumns) { @@ -59,6 +69,7 @@ TEST(CursorWindowTest, SetNumColumns) { ASSERT_NE(w->setNumColumns(5), OK); ASSERT_NE(w->setNumColumns(3), OK); ASSERT_EQ(w->getNumColumns(), 4); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, SetNumColumnsAfterRow) { @@ -69,6 +80,7 @@ TEST(CursorWindowTest, SetNumColumnsAfterRow) { ASSERT_EQ(w->allocRow(), OK); ASSERT_NE(w->setNumColumns(4), OK); ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, AllocRow) { @@ -82,14 +94,17 @@ TEST(CursorWindowTest, AllocRow) { ASSERT_EQ(w->allocRow(), OK); ASSERT_LT(w->freeSpace(), before); ASSERT_EQ(w->getNumRows(), 1); + ASSERT_ALIGNED(w); // Verify we can unwind ASSERT_EQ(w->freeLastRow(), OK); ASSERT_EQ(w->freeSpace(), before); ASSERT_EQ(w->getNumRows(), 0); + ASSERT_ALIGNED(w); // Can't unwind when no rows left ASSERT_NE(w->freeLastRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, AllocRowBounds) { @@ -99,6 +114,7 @@ TEST(CursorWindowTest, AllocRowBounds) { ASSERT_EQ(w->setNumColumns(60), OK); ASSERT_EQ(w->allocRow(), OK); ASSERT_NE(w->allocRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreNull) { @@ -115,6 +131,7 @@ TEST(CursorWindowTest, StoreNull) { auto field = w->getFieldSlot(0, 0); ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreLong) { @@ -133,6 +150,7 @@ TEST(CursorWindowTest, StoreLong) { ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreString) { @@ -154,6 +172,7 @@ TEST(CursorWindowTest, StoreString) { auto actual = w->getFieldSlotValueString(field, &size); ASSERT_EQ(std::string(actual), "cafe"); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, StoreBounds) { @@ -174,6 +193,7 @@ TEST(CursorWindowTest, StoreBounds) { ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr); ASSERT_EQ(w->getFieldSlot(0, -1), nullptr); ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, Inflate) { @@ -233,6 +253,7 @@ TEST(CursorWindowTest, Inflate) { ASSERT_NE(actual, buf); ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelEmpty) { @@ -248,10 +269,12 @@ TEST(CursorWindowTest, ParcelEmpty) { ASSERT_EQ(w->getNumColumns(), 0); ASSERT_EQ(w->size(), 0); ASSERT_EQ(w->freeSpace(), 0); + ASSERT_ALIGNED(w); // We can't mutate the window after parceling ASSERT_NE(w->setNumColumns(4), OK); ASSERT_NE(w->allocRow(), OK); + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelSmall) { @@ -310,6 +333,7 @@ TEST(CursorWindowTest, ParcelSmall) { ASSERT_EQ(actualSize, 0); ASSERT_NE(actual, nullptr); } + ASSERT_ALIGNED(w); } TEST(CursorWindowTest, ParcelLarge) { @@ -362,6 +386,7 @@ TEST(CursorWindowTest, ParcelLarge) { ASSERT_EQ(actualSize, 0); ASSERT_NE(actual, nullptr); } + ASSERT_ALIGNED(w); } } // android From 3a54ae3ea081df451a1cec96f480f55d43fa4470 Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Fri, 28 Apr 2023 16:17:16 +0000 Subject: [PATCH 60/61] Verify URI permissions in MediaMetadata Add a check for URI permission to make sure that user can access the URI set in MediaMetadata. If permission is denied, clear the URI string set in metadata. Bug: 271851153 Test: atest MediaSessionTest Test: Verified by POC app attached in bug, image of second user is not the UMO background of the first user. (cherry picked from commit b8a7fd8e6f41ee54d27c1e7aaa15b4a3f5365a02) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:91705f7cc95a87a5cc7814f543669adcd3b35f09) Merged-In: I384f8e230c909d8fc8e5f147e2fd3558fec44626 Change-Id: I384f8e230c909d8fc8e5f147e2fd3558fec44626 --- .../server/media/MediaSessionRecord.java | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 31ad43b5a409..66adbad5372e 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -22,6 +22,8 @@ import android.annotation.RequiresPermission; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -63,6 +65,9 @@ import android.util.Log; import android.view.KeyEvent; +import com.android.server.LocalServices; +import com.android.server.uri.UriGrantsManagerInternal; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -77,6 +82,10 @@ // TODO(jaewan): Do not call service method directly -- introduce listener instead. public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { private static final String TAG = "MediaSessionRecord"; + private static final String[] ART_URIS = new String[] { + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + MediaMetadata.METADATA_KEY_ART_URI, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI}; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** @@ -130,6 +139,7 @@ private static int getVolumeStream(@Nullable AudioAttributes attr) { private final SessionStub mSession; private final SessionCb mSessionCb; private final MediaSessionService mService; + private final UriGrantsManagerInternal mUgmInternal; private final Context mContext; private final boolean mVolumeAdjustmentForRemoteGroupSessions; @@ -193,6 +203,7 @@ public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPa mPolicies = policies; mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); + mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -1013,21 +1024,45 @@ public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException { public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) throws RemoteException { synchronized (mLock) { - MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata) - .build(); - // This is to guarantee that the underlying bundle is unparceled - // before we set it to prevent concurrent reads from throwing an - // exception - if (temp != null) { - temp.size(); - } - mMetadata = temp; mDuration = duration; mMetadataDescription = metadataDescription; + mMetadata = sanitizeMediaMetadata(metadata); } mHandler.post(MessageHandler.MSG_UPDATE_METADATA); } + private MediaMetadata sanitizeMediaMetadata(MediaMetadata metadata) { + if (metadata == null) { + return null; + } + MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(metadata); + for (String key: ART_URIS) { + String uriString = metadata.getString(key); + if (TextUtils.isEmpty(uriString)) { + continue; + } + Uri uri = Uri.parse(uriString); + if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + continue; + } + try { + mUgmInternal.checkGrantUriPermission(getUid(), + getPackageName(), + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, getUserId())); + } catch (SecurityException e) { + metadataBuilder.putString(key, null); + } + } + MediaMetadata sanitizedMetadata = metadataBuilder.build(); + // sanitizedMetadata.size() guarantees that the underlying bundle is unparceled + // before we set it to prevent concurrent reads from throwing an + // exception + sanitizedMetadata.size(); + return sanitizedMetadata; + } + @Override public void setPlaybackState(PlaybackState state) throws RemoteException { int oldState = mPlaybackState == null From 3fa0e423d4581147fd4ad003a61eafb2b457c1e7 Mon Sep 17 00:00:00 2001 From: Pranav Madapurmath Date: Sat, 3 Jun 2023 01:09:31 +0000 Subject: [PATCH 61/61] Merge "Resolve StatusHints image exploit across user." into rvc-dev am: 543e6febbf am: 8c3d465b5e Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23438530 Fixes: 285650146 Fixes: 280797684 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:06456af560729b8a8d209613bb117ede3496fd9d) Merged-In: I7822bf2bb75c775faaaa7023fd2c9af9f6d6888f Change-Id: I7822bf2bb75c775faaaa7023fd2c9af9f6d6888f --- .../android/telecom/ParcelableConference.java | 12 ++++- .../java/android/telecom/StatusHints.java | 53 ++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 1f8aafbca476..77034041f1fd 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -21,12 +21,12 @@ import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telecom.IVideoProvider; + import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.android.internal.telecom.IVideoProvider; - /** * A parcelable representation of a conference connection. * @hide @@ -287,6 +287,14 @@ public int getCallDirection() { return mCallDirection; } + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator () { @Override diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 762c93a49022..b7346331dc60 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -16,14 +16,19 @@ package android.telecom; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Binder; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; import java.util.Objects; @@ -33,7 +38,7 @@ public final class StatusHints implements Parcelable { private final CharSequence mLabel; - private final Icon mIcon; + private Icon mIcon; private final Bundle mExtras; /** @@ -48,10 +53,30 @@ public StatusHints(ComponentName packageName, CharSequence label, int iconResId, public StatusHints(CharSequence label, Icon icon, Bundle extras) { mLabel = label; - mIcon = icon; + mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle()); mExtras = extras; } + /** + * @param icon + * @hide + */ + @VisibleForTesting + public StatusHints(@Nullable Icon icon) { + mLabel = null; + mExtras = null; + mIcon = icon; + } + + /** + * + * @param icon + * @hide + */ + public void setIcon(@Nullable Icon icon) { + mIcon = icon; + } + /** * @return A package used to load the icon. * @@ -112,6 +137,30 @@ public int describeContents() { return 0; } + /** + * Validates the StatusHints image icon to see if it's not in the calling user space. + * Invalidates the icon if so, otherwise returns back the original icon. + * + * @param icon + * @return icon (validated) + * @hide + */ + public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) { + // Refer to Icon#getUriString for context. The URI string is invalid for icons of + // incompatible types. + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { + String encodedUser = icon.getUri().getEncodedUserInfo(); + // If there is no encoded user, the URI is calling into the calling user space + if (encodedUser != null) { + int userId = Integer.parseInt(encodedUser); + // Do not try to save the icon if the user id isn't in the calling user space. + if (userId != callingUserHandle.getIdentifier()) return null; + } + } + return icon; + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeCharSequence(mLabel);