Loading services/core/java/com/android/server/wm/TaskFragment.java +9 −0 Original line number Diff line number Diff line Loading @@ -524,7 +524,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { || isAllowedToEmbedActivityInTrustedMode(a); } /** * Checks if the organized task fragment is allowed to embed activity in untrusted mode. */ boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) { final WindowContainer parent = getParent(); if (parent == null || !parent.getBounds().contains(getBounds())) { // Without full trust between the host and the embedded activity, we don't allow // TaskFragment to have bounds outside of the parent bounds. return false; } return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; } Loading services/core/java/com/android/server/wm/WindowOrganizerController.java +82 −44 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.isStartResultSuccessful; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION; import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; Loading Loading @@ -681,7 +679,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } } effects |= deleteTaskFragment(taskFragment, errorCallbackToken); effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken); break; } case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { Loading @@ -699,8 +697,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub .startActivityInTaskFragment(tf, activityIntent, activityOptions, hop.getCallingActivity(), caller.mUid, caller.mPid); if (!isStartResultSuccessful(result)) { sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), errorCallbackToken, sendTaskFragmentOperationFailure(organizer, errorCallbackToken, convertStartFailureToThrowable(result, activityIntent)); } else { effects |= TRANSACT_EFFECTS_LIFECYCLE; Loading @@ -710,13 +707,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { final IBinder fragmentToken = hop.getNewParent(); final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { final TaskFragment parent = mLaunchTaskFragments.get(fragmentToken); if (parent == null || activity == null) { final Throwable exception = new IllegalArgumentException( "Not allowed to operate with invalid fragment token or activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); if (!parent.isAllowedToEmbedActivity(activity)) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } activity.reparent(parent, POSITION_TOP); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } Loading Loading @@ -860,12 +864,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final WindowContainer newParent = hop.getNewParent() != null ? WindowContainer.fromBinder(hop.getNewParent()) : null; if (oldParent == null || !oldParent.isAttached()) { if (oldParent == null || oldParent.asTaskFragment() == null || !oldParent.isAttached()) { Slog.e(TAG, "Attempt to operate on unknown or detached container: " + oldParent); break; } reparentTaskFragment(oldParent, newParent, errorCallbackToken); reparentTaskFragment(oldParent.asTaskFragment(), newParent, organizer, errorCallbackToken); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } Loading Loading @@ -1246,7 +1252,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.enforceTaskPermission(func); } private void enforceTaskPermission(String func, WindowContainerTransaction t) { private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) { if (t == null || t.getTaskFragmentOrganizer() == null) { enforceTaskPermission(func); return; Loading @@ -1271,14 +1277,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); // Only allow to apply changes to TaskFragment that is created by this organizer. WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); enforceTaskFragmentOrganized(func, wc, organizer); enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer); } // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one // could first change a trusted TF, and then start/reparent untrusted activity there. // Hierarchy changes final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); for (int i = hops.size() - 1; i >= 0; i--) { Loading Loading @@ -1352,8 +1355,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the * parent bounds are not allowed for embedding without full trust between the host and the * target. * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from * tapjacking. */ private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc, WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) { Loading @@ -1361,35 +1362,48 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Attempt to operate on task fragment that no longer exists"); return; } // Check if TaskFragment is embedded in fully trusted mode if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { // Fully trusted, no need to check further return; } if (change == null) { return; } final int changeMask = change.getChangeMask(); if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) { if (changeMask != 0) { // None of the change should be requested from a TaskFragment organizer. String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply SurfaceControl changes to TaskFragment in non-trusted " + "embedding mode, TaskFragmentOrganizer=" + organizer; + " trying to apply changes of " + changeMask + " to TaskFragment" + " TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } if (change.getWindowSetMask() == 0) { // Nothing else to check. // Check if TaskFragment is embedded in fully trusted mode. if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { // Fully trusted, no need to check further return; } WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration; WindowContainer wcParent = wc.getParent(); final WindowContainer wcParent = wc.getParent(); if (wcParent == null) { Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent"); Slog.e(TAG, "Attempt to apply config change on task fragment that has no parent"); return; } if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) { final Configuration requestedConfig = change.getConfiguration(); final Configuration parentConfig = wcParent.getConfiguration(); if (parentConfig.screenWidthDp < requestedConfig.screenWidthDp || parentConfig.screenHeightDp < requestedConfig.screenHeightDp || parentConfig.smallestScreenWidthDp < requestedConfig.smallestScreenWidthDp) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply screen width/height greater than parent's for non-trusted" + " host, TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } if (change.getWindowSetMask() == 0) { // No bounds change. return; } final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration; final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration; if (!parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply bounds outside of parent for non-trusted host," Loading @@ -1397,6 +1411,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.w(TAG, msg); throw new SecurityException(msg); } if (requestedWindowConfig.getAppBounds() != null && parentWindowConfig.getAppBounds() != null && !parentWindowConfig.getAppBounds().contains( requestedWindowConfig.getAppBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply app bounds outside of parent for non-trusted host," + " TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } } void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, Loading @@ -1422,7 +1447,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() || ownerActivity.getTask().effectiveUid != caller.mUid) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with the ownerToken while " new SecurityException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; Loading @@ -1439,33 +1464,46 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); } void reparentTaskFragment(@NonNull WindowContainer oldParent, @Nullable WindowContainer newParent, @Nullable IBinder errorCallbackToken) { WindowContainer parent = newParent; if (parent == null && oldParent.asTaskFragment() != null) { parent = oldParent.asTaskFragment().getTask(); void reparentTaskFragment(@NonNull TaskFragment oldParent, @Nullable WindowContainer newParent, @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { final TaskFragment newParentTF; if (newParent == null) { // Use the old parent's parent if the caller doesn't specify the new parent. newParentTF = oldParent.getTask(); } else { newParentTF = newParent.asTaskFragment(); } if (parent == null) { if (newParentTF == null) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid container"); sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(), errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } if (newParentTF.getTaskFragmentOrganizer() != null) { // We are reparenting activities to a new embedded TaskFragment, this operation is only // allowed if the new parent is trusted by all reparent activities. final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> !newParentTF.isAllowedToEmbedActivity(activity)); if (isEmbeddingDisallowed) { final Throwable exception = new SecurityException( "The new parent is not trusted to embed the activities."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } } while (oldParent.hasChild()) { oldParent.getChildAt(0).reparent(parent, POSITION_TOP); oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP); } } private int deleteTaskFragment(@NonNull TaskFragment taskFragment, @Nullable IBinder errorCallbackToken) { @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { final int index = mLaunchTaskFragments.indexOfValue(taskFragment); if (index < 0) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid " + "taskFragment"); sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(), errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return 0; } mLaunchTaskFragments.removeAt(index); Loading services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +93 −41 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; Loading Loading @@ -249,19 +250,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); } @Test Loading @@ -272,19 +267,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reorder(mFragmentWindowToken, true /* onTop */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); } @Test Loading @@ -298,27 +287,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.deleteTaskFragment(mFragmentWindowToken); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); // No lifecycle update when the TaskFragment is not recorded. verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities(); mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading @@ -335,13 +318,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, Loading @@ -350,7 +327,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading Loading @@ -423,20 +400,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading @@ -454,6 +425,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token); doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); Loading Loading @@ -574,6 +546,66 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); } /** * For config change to untrusted embedded TaskFragment, we only allow bounds change within * its parent bounds. */ @Test public void testUntrustedEmbedding_configChange() throws RemoteException { mController.registerOrganizer(mIOrganizer); mOrganizer.applyTransaction(mTransaction); mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode(); final Task task = createTask(mDisplayContent); final Rect taskBounds = new Rect(task.getBounds()); final Rect taskAppBounds = new Rect(task.getWindowConfiguration().getAppBounds()); final int taskScreenWidthDp = task.getConfiguration().screenWidthDp; final int taskScreenHeightDp = task.getConfiguration().screenHeightDp; final int taskSmallestScreenWidthDp = task.getConfiguration().smallestScreenWidthDp; task.addChild(mTaskFragment, POSITION_TOP); // Throw exception if the transaction is trying to change bounds of an untrusted outside of // its parent's. // setBounds final Rect tfBounds = new Rect(taskBounds); tfBounds.right++; mTransaction.setBounds(mFragmentWindowToken, tfBounds); assertApplyTransactionDisallowed(mTransaction); mTransaction.setBounds(mFragmentWindowToken, taskBounds); assertApplyTransactionAllowed(mTransaction); // setAppBounds final Rect tfAppBounds = new Rect(taskAppBounds); tfAppBounds.right++; mTransaction.setAppBounds(mFragmentWindowToken, tfAppBounds); assertApplyTransactionDisallowed(mTransaction); mTransaction.setAppBounds(mFragmentWindowToken, taskAppBounds); assertApplyTransactionAllowed(mTransaction); // setScreenSizeDp mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp + 1, taskScreenHeightDp + 1); assertApplyTransactionDisallowed(mTransaction); mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp, taskScreenHeightDp); assertApplyTransactionAllowed(mTransaction); // setSmallestScreenWidthDp mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp + 1); assertApplyTransactionDisallowed(mTransaction); mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp); assertApplyTransactionAllowed(mTransaction); // Any of the change mask is not allowed. mTransaction.setFocusable(mFragmentWindowToken, false); assertApplyTransactionDisallowed(mTransaction); } /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, Loading @@ -590,4 +622,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. wct.createTaskFragment(params); } /** Asserts that applying the given transaction will throw a {@link SecurityException}. */ private void assertApplyTransactionDisallowed(WindowContainerTransaction t) { assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(t); } catch (RemoteException e) { fail(); } }); } /** Asserts that applying the given transaction will not throw any exception. */ private void assertApplyTransactionAllowed(WindowContainerTransaction t) { try { mAtm.getWindowOrganizerController().applyTransaction(t); } catch (RemoteException e) { fail(); } } } services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +1 −0 Original line number Diff line number Diff line Loading @@ -1264,6 +1264,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); } spyOn(taskFragment); return taskFragment; } } Loading Loading
services/core/java/com/android/server/wm/TaskFragment.java +9 −0 Original line number Diff line number Diff line Loading @@ -524,7 +524,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { || isAllowedToEmbedActivityInTrustedMode(a); } /** * Checks if the organized task fragment is allowed to embed activity in untrusted mode. */ boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) { final WindowContainer parent = getParent(); if (parent == null || !parent.getBounds().contains(getBounds())) { // Without full trust between the host and the embedded activity, we don't allow // TaskFragment to have bounds outside of the parent bounds. return false; } return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; } Loading
services/core/java/com/android/server/wm/WindowOrganizerController.java +82 −44 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.isStartResultSuccessful; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION; import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; Loading Loading @@ -681,7 +679,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } } effects |= deleteTaskFragment(taskFragment, errorCallbackToken); effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken); break; } case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { Loading @@ -699,8 +697,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub .startActivityInTaskFragment(tf, activityIntent, activityOptions, hop.getCallingActivity(), caller.mUid, caller.mPid); if (!isStartResultSuccessful(result)) { sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), errorCallbackToken, sendTaskFragmentOperationFailure(organizer, errorCallbackToken, convertStartFailureToThrowable(result, activityIntent)); } else { effects |= TRANSACT_EFFECTS_LIFECYCLE; Loading @@ -710,13 +707,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { final IBinder fragmentToken = hop.getNewParent(); final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { final TaskFragment parent = mLaunchTaskFragments.get(fragmentToken); if (parent == null || activity == null) { final Throwable exception = new IllegalArgumentException( "Not allowed to operate with invalid fragment token or activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); if (!parent.isAllowedToEmbedActivity(activity)) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } activity.reparent(parent, POSITION_TOP); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } Loading Loading @@ -860,12 +864,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final WindowContainer newParent = hop.getNewParent() != null ? WindowContainer.fromBinder(hop.getNewParent()) : null; if (oldParent == null || !oldParent.isAttached()) { if (oldParent == null || oldParent.asTaskFragment() == null || !oldParent.isAttached()) { Slog.e(TAG, "Attempt to operate on unknown or detached container: " + oldParent); break; } reparentTaskFragment(oldParent, newParent, errorCallbackToken); reparentTaskFragment(oldParent.asTaskFragment(), newParent, organizer, errorCallbackToken); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } Loading Loading @@ -1246,7 +1252,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.enforceTaskPermission(func); } private void enforceTaskPermission(String func, WindowContainerTransaction t) { private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) { if (t == null || t.getTaskFragmentOrganizer() == null) { enforceTaskPermission(func); return; Loading @@ -1271,14 +1277,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); // Only allow to apply changes to TaskFragment that is created by this organizer. WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); enforceTaskFragmentOrganized(func, wc, organizer); enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer); } // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one // could first change a trusted TF, and then start/reparent untrusted activity there. // Hierarchy changes final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); for (int i = hops.size() - 1; i >= 0; i--) { Loading Loading @@ -1352,8 +1355,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the * parent bounds are not allowed for embedding without full trust between the host and the * target. * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from * tapjacking. */ private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc, WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) { Loading @@ -1361,35 +1362,48 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Attempt to operate on task fragment that no longer exists"); return; } // Check if TaskFragment is embedded in fully trusted mode if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { // Fully trusted, no need to check further return; } if (change == null) { return; } final int changeMask = change.getChangeMask(); if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) { if (changeMask != 0) { // None of the change should be requested from a TaskFragment organizer. String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply SurfaceControl changes to TaskFragment in non-trusted " + "embedding mode, TaskFragmentOrganizer=" + organizer; + " trying to apply changes of " + changeMask + " to TaskFragment" + " TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } if (change.getWindowSetMask() == 0) { // Nothing else to check. // Check if TaskFragment is embedded in fully trusted mode. if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) { // Fully trusted, no need to check further return; } WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration; WindowContainer wcParent = wc.getParent(); final WindowContainer wcParent = wc.getParent(); if (wcParent == null) { Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent"); Slog.e(TAG, "Attempt to apply config change on task fragment that has no parent"); return; } if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) { final Configuration requestedConfig = change.getConfiguration(); final Configuration parentConfig = wcParent.getConfiguration(); if (parentConfig.screenWidthDp < requestedConfig.screenWidthDp || parentConfig.screenHeightDp < requestedConfig.screenHeightDp || parentConfig.smallestScreenWidthDp < requestedConfig.smallestScreenWidthDp) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply screen width/height greater than parent's for non-trusted" + " host, TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } if (change.getWindowSetMask() == 0) { // No bounds change. return; } final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration; final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration; if (!parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply bounds outside of parent for non-trusted host," Loading @@ -1397,6 +1411,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.w(TAG, msg); throw new SecurityException(msg); } if (requestedWindowConfig.getAppBounds() != null && parentWindowConfig.getAppBounds() != null && !parentWindowConfig.getAppBounds().contains( requestedWindowConfig.getAppBounds())) { String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " trying to apply app bounds outside of parent for non-trusted host," + " TaskFragmentOrganizer=" + organizer; Slog.w(TAG, msg); throw new SecurityException(msg); } } void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, Loading @@ -1422,7 +1447,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid() || ownerActivity.getTask().effectiveUid != caller.mUid) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with the ownerToken while " new SecurityException("Not allowed to operate with the ownerToken while " + "the root activity of the target task belong to the different app"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; Loading @@ -1439,33 +1464,46 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment); } void reparentTaskFragment(@NonNull WindowContainer oldParent, @Nullable WindowContainer newParent, @Nullable IBinder errorCallbackToken) { WindowContainer parent = newParent; if (parent == null && oldParent.asTaskFragment() != null) { parent = oldParent.asTaskFragment().getTask(); void reparentTaskFragment(@NonNull TaskFragment oldParent, @Nullable WindowContainer newParent, @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { final TaskFragment newParentTF; if (newParent == null) { // Use the old parent's parent if the caller doesn't specify the new parent. newParentTF = oldParent.getTask(); } else { newParentTF = newParent.asTaskFragment(); } if (parent == null) { if (newParentTF == null) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid container"); sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(), errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } if (newParentTF.getTaskFragmentOrganizer() != null) { // We are reparenting activities to a new embedded TaskFragment, this operation is only // allowed if the new parent is trusted by all reparent activities. final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> !newParentTF.isAllowedToEmbedActivity(activity)); if (isEmbeddingDisallowed) { final Throwable exception = new SecurityException( "The new parent is not trusted to embed the activities."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return; } } while (oldParent.hasChild()) { oldParent.getChildAt(0).reparent(parent, POSITION_TOP); oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP); } } private int deleteTaskFragment(@NonNull TaskFragment taskFragment, @Nullable IBinder errorCallbackToken) { @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) { final int index = mLaunchTaskFragments.indexOfValue(taskFragment); if (index < 0) { final Throwable exception = new IllegalArgumentException("Not allowed to operate with invalid " + "taskFragment"); sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(), errorCallbackToken, exception); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return 0; } mLaunchTaskFragments.removeAt(index); Loading
services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +93 −41 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; import static org.junit.Assert.assertEquals; Loading Loading @@ -249,19 +250,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); } @Test Loading @@ -272,19 +267,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reorder(mFragmentWindowToken, true /* onTop */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); } @Test Loading @@ -298,27 +287,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.deleteTaskFragment(mFragmentWindowToken); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); // No lifecycle update when the TaskFragment is not recorded. verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities(); mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading @@ -335,13 +318,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, Loading @@ -350,7 +327,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading Loading @@ -423,20 +400,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // the organizer. mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */); assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } catch (RemoteException e) { fail(); } }); assertApplyTransactionDisallowed(mTransaction); // Allow transaction to change a TaskFragment created by the organizer. mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } Loading @@ -454,6 +425,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mAtm.mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token); doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); Loading Loading @@ -574,6 +546,66 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); } /** * For config change to untrusted embedded TaskFragment, we only allow bounds change within * its parent bounds. */ @Test public void testUntrustedEmbedding_configChange() throws RemoteException { mController.registerOrganizer(mIOrganizer); mOrganizer.applyTransaction(mTransaction); mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode(); final Task task = createTask(mDisplayContent); final Rect taskBounds = new Rect(task.getBounds()); final Rect taskAppBounds = new Rect(task.getWindowConfiguration().getAppBounds()); final int taskScreenWidthDp = task.getConfiguration().screenWidthDp; final int taskScreenHeightDp = task.getConfiguration().screenHeightDp; final int taskSmallestScreenWidthDp = task.getConfiguration().smallestScreenWidthDp; task.addChild(mTaskFragment, POSITION_TOP); // Throw exception if the transaction is trying to change bounds of an untrusted outside of // its parent's. // setBounds final Rect tfBounds = new Rect(taskBounds); tfBounds.right++; mTransaction.setBounds(mFragmentWindowToken, tfBounds); assertApplyTransactionDisallowed(mTransaction); mTransaction.setBounds(mFragmentWindowToken, taskBounds); assertApplyTransactionAllowed(mTransaction); // setAppBounds final Rect tfAppBounds = new Rect(taskAppBounds); tfAppBounds.right++; mTransaction.setAppBounds(mFragmentWindowToken, tfAppBounds); assertApplyTransactionDisallowed(mTransaction); mTransaction.setAppBounds(mFragmentWindowToken, taskAppBounds); assertApplyTransactionAllowed(mTransaction); // setScreenSizeDp mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp + 1, taskScreenHeightDp + 1); assertApplyTransactionDisallowed(mTransaction); mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp, taskScreenHeightDp); assertApplyTransactionAllowed(mTransaction); // setSmallestScreenWidthDp mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp + 1); assertApplyTransactionDisallowed(mTransaction); mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp); assertApplyTransactionAllowed(mTransaction); // Any of the change mask is not allowed. mTransaction.setFocusable(mFragmentWindowToken, false); assertApplyTransactionDisallowed(mTransaction); } /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction} to apply the transaction, Loading @@ -590,4 +622,24 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. wct.createTaskFragment(params); } /** Asserts that applying the given transaction will throw a {@link SecurityException}. */ private void assertApplyTransactionDisallowed(WindowContainerTransaction t) { assertThrows(SecurityException.class, () -> { try { mAtm.getWindowOrganizerController().applyTransaction(t); } catch (RemoteException e) { fail(); } }); } /** Asserts that applying the given transaction will not throw any exception. */ private void assertApplyTransactionAllowed(WindowContainerTransaction t) { try { mAtm.getWindowOrganizerController().applyTransaction(t); } catch (RemoteException e) { fail(); } } }
services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +1 −0 Original line number Diff line number Diff line Loading @@ -1264,6 +1264,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); } spyOn(taskFragment); return taskFragment; } } Loading