From e3f6b7e16af017c98b4f90540ee6671d1ce48550 Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Tue, 17 May 2022 11:02:13 +0100 Subject: [PATCH] [Media Projection] Require valid token to set session When MediaProjection sets the session details in MediaProjectiondManagerService, clear and re-set the calling identity (since we have entered the system server across the aidl boundary). Additionaly, verify that the call originated from a valid MediaProjection session. In the current model for MediaProjection, signature-level permission MANAGE_MEDIA_PROJECTION is held by the component that shows the acceptance dialog to the user. The user allowing some app to capture with MediaProjection is represented by the IMediaProjection token (see MediaProjectionManagerService# isValidMediaProjection). Bug: 230748205 Test: Manual Change-Id: Iace8eb7eea6c7a99fba7ea726481461a11bd1c90 --- core/java/android/view/IWindowManager.aidl | 12 ---------- .../projection/IMediaProjectionManager.aidl | 12 ++++++++++ .../media/projection/MediaProjection.java | 15 ++++++++---- .../MediaProjectionManagerService.java | 24 ++++++++++++++++++- .../android/server/wm/ContentRecorder.java | 3 ++- .../server/wm/WindowManagerInternal.java | 13 ++++++++++ .../server/wm/WindowManagerService.java | 18 +++++++------- .../server/wm/DisplayContentTests.java | 4 ++-- 8 files changed, 71 insertions(+), 30 deletions(-) diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c83869c9ee686..fb562d8e97db8 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -32,7 +32,6 @@ import android.graphics.Region; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; -import android.view.ContentRecordingSession; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.IAppTransitionAnimationSpecsFuture; @@ -873,17 +872,6 @@ interface IWindowManager */ void detachWindowContextFromWindowContainer(IBinder clientToken); - /** - * Updates the content recording session. If a different session is already in progress, then - * the pre-existing session is stopped, and the new incoming session takes over. - * - * The DisplayContent for the new session will begin recording when - * {@link RootWindowContainer#onDisplayChanged} is invoked for the new {@link VirtualDisplay}. - * - * @param incomingSession the nullable incoming content recording session - */ - void setContentRecordingSession(in ContentRecordingSession incomingSession); - /** * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled. * diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d190fcec6c8c8..1d58a409718de 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -21,6 +21,7 @@ import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionWatcherCallback; import android.media.projection.MediaProjectionInfo; import android.os.IBinder; +import android.view.ContentRecordingSession; /** {@hide} */ interface IMediaProjectionManager { @@ -33,4 +34,15 @@ interface IMediaProjectionManager { void stopActiveProjection(); void addCallback(IMediaProjectionWatcherCallback callback); void removeCallback(IMediaProjectionWatcherCallback callback); + + /** + * Updates the content recording session. If a different session is already in progress, then + * the pre-existing session is stopped, and the new incoming session takes over. Only updates + * the session if the given projection is valid. + * + * @param incomingSession the nullable incoming content recording session + * @param projection the non-null projection the session describes + */ + void setContentRecordingSession(in ContentRecordingSession incomingSession, + in IMediaProjection projection); } diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index b5f95938f8458..ba7bf3f5f5d32 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -26,12 +26,11 @@ import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.os.Handler; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.view.ContentRecordingSession; -import android.view.IWindowManager; import android.view.Surface; -import android.view.WindowManagerGlobal; import android.window.WindowContainerToken; import java.util.Map; @@ -53,6 +52,7 @@ public final class MediaProjection { private final IMediaProjection mImpl; private final Context mContext; private final Map mCallbacks; + @Nullable private IMediaProjectionManager mProjectionService = null; /** @hide */ public MediaProjection(Context context, IMediaProjection impl) { @@ -172,7 +172,6 @@ public final class MediaProjection { @NonNull VirtualDisplayConfig.Builder virtualDisplayConfig, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { try { - final IWindowManager wmService = WindowManagerGlobal.getWindowManagerService(); final WindowContainerToken taskWindowContainerToken = mImpl.getTaskRecordingWindowContainerToken(); Context windowContext = null; @@ -199,7 +198,7 @@ public final class MediaProjection { } session.setDisplayId(virtualDisplay.getDisplay().getDisplayId()); // Successfully set up, so save the current session details. - wmService.setContentRecordingSession(session); + getProjectionService().setContentRecordingSession(session, mImpl); return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. @@ -207,6 +206,14 @@ public final class MediaProjection { } } + private IMediaProjectionManager getProjectionService() { + if (mProjectionService == null) { + mProjectionService = IMediaProjectionManager.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); + } + return mProjectionService; + } + /** * Stops projection. */ diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 1937852fa3336..098e8f74749ca 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -45,14 +45,15 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; +import android.view.ContentRecordingSession; import android.window.WindowContainerToken; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.Watchdog; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -381,6 +382,27 @@ public final class MediaProjectionManagerService extends SystemService } } + /** + * Updates the current content mirroring session. + */ + @Override + public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession, + @NonNull IMediaProjection projection) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + if (!isValidMediaProjection(projection)) { + throw new SecurityException("Invalid media projection"); + } + LocalServices.getService( + WindowManagerInternal.class).setContentRecordingSession( + incomingSession); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 87523f44d4b67..5d2d5826f227d 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -207,7 +207,8 @@ final class ContentRecorder { // Update the cached session state first, since updating the service will result in always // returning to this instance to update recording state. mContentRecordingSession = null; - mDisplayContent.mWmService.setContentRecordingSession(null); + mDisplayContent.mWmService.mContentRecordingController.setContentRecordingSessionLocked( + null, mDisplayContent.mWmService); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index c0d7d1362ac33..9eee7ba871a21 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; import android.util.Pair; +import android.view.ContentRecordingSession; import android.view.Display; import android.view.IInputFilter; import android.view.IRemoteAnimationFinishedCallback; @@ -869,4 +870,16 @@ public abstract class WindowManagerInternal { */ public abstract boolean isPointInsideWindow( @NonNull IBinder windowToken, int displayId, float displayX, float displayY); + + /** + * Updates the content recording session. If a different session is already in progress, then + * the pre-existing session is stopped, and the new incoming session takes over. + * + * The DisplayContent for the new session will begin recording when + * {@link RootWindowContainer#onDisplayChanged} is invoked for the new {@link VirtualDisplay}. + * Must be invoked for a valid MediaProjection session. + * + * @param incomingSession the nullable incoming content recording session + */ + public abstract void setContentRecordingSession(ContentRecordingSession incomingSession); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7a5480401de82..902218621cd00 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2956,16 +2956,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Updates the current content mirroring session. - */ - @Override - public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) { - synchronized (mGlobalLock) { - mContentRecordingController.setContentRecordingSessionLocked(incomingSession, this); - } - } - // TODO(multi-display): remove when no default display use case. void prepareAppTransitionNone() { if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) { @@ -8242,6 +8232,14 @@ public class WindowManagerService extends IWindowManager.Stub return w.getBounds().contains((int) displayX, (int) displayY); } } + + @Override + public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) { + synchronized (mGlobalLock) { + mContentRecordingController.setContentRecordingSessionLocked(incomingSession, + WindowManagerService.this); + } + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index c5f785ea76802..32f3bfe5166c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2441,7 +2441,7 @@ public class DisplayContentTests extends WindowTestsBase { ContentRecordingSession session = ContentRecordingSession.createDisplaySession( tokenToMirror); session.setDisplayId(displayId); - mWm.setContentRecordingSession(session); + mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm); actualDC.updateRecording(); // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off. @@ -2470,7 +2470,7 @@ public class DisplayContentTests extends WindowTestsBase { ContentRecordingSession session = ContentRecordingSession.createDisplaySession( tokenToMirror); session.setDisplayId(displayId); - mWm.setContentRecordingSession(session); + mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm); mWm.mRoot.onDisplayAdded(displayId); // WHEN getting the DisplayContent for the new virtual display.