When setting camera, ensure calling user is the same as logged in user.
When the VideoProvider API is used to change the camera, in addition to
performing permission checks on the caller, adding logic to ensure that
the calling UserHandle is the currently logged in User (this is to ensure
that a background app can't access the camera).
Test: Unit, manual
Bug: 32747443
Change-Id: I0856f991c520dd1f5fa0146919941c052cba8a01
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 61dd99c..4b86738 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -2008,7 +2008,8 @@
if (videoProvider != null ) {
try {
- mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
+ mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this,
+ mCallsManager);
} catch (RemoteException ignored) {
// Ignore RemoteException.
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 9e6ea16..895e848 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -96,7 +96,7 @@
*/
@VisibleForTesting
public class CallsManager extends Call.ListenerBase
- implements VideoProviderProxy.Listener, CallFilterResultCallback {
+ implements VideoProviderProxy.Listener, CallFilterResultCallback, CurrentUserProxy {
// TODO: Consider renaming this CallsManagerPlugin.
@VisibleForTesting
@@ -615,6 +615,7 @@
return mCallAudioManager.getForegroundCall();
}
+ @Override
public UserHandle getCurrentUserHandle() {
return mCurrentUserHandle;
}
@@ -2239,7 +2240,8 @@
* Callback when foreground user is switched. We will reload missed call in all profiles
* including the user itself. There may be chances that profiles are not started yet.
*/
- void onUserSwitch(UserHandle userHandle) {
+ @VisibleForTesting
+ public void onUserSwitch(UserHandle userHandle) {
mCurrentUserHandle = userHandle;
mMissedCallNotifier.setCurrentUserHandle(userHandle);
final UserManager userManager = UserManager.get(mContext);
diff --git a/src/com/android/server/telecom/CurrentUserProxy.java b/src/com/android/server/telecom/CurrentUserProxy.java
new file mode 100644
index 0000000..61dce61
--- /dev/null
+++ b/src/com/android/server/telecom/CurrentUserProxy.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 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.telecom;
+
+import android.os.UserHandle;
+
+/**
+ * Defines common functionality for a class which has knowledge of the currently logged in user.
+ * Implemented by {@link CallsManager} and used by {@link VideoProviderProxy} so that it does not
+ * have a dependency on the entire CallsManager when all that is required is the ability to find out
+ * the handle of the currently logged in user.
+ */
+public interface CurrentUserProxy {
+ UserHandle getCurrentUserHandle();
+}
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index b537df9..480d8c9 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.telecom.Connection;
import android.telecom.InCallService;
import android.telecom.Log;
@@ -88,6 +89,11 @@
*/
private Call mCall;
+ /**
+ * Interface providing access to the currently logged in user.
+ */
+ private CurrentUserProxy mCurrentUserProxy;
+
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -106,7 +112,8 @@
* @throws RemoteException Remote exception.
*/
VideoProviderProxy(TelecomSystem.SyncRoot lock,
- IVideoProvider videoProvider, Call call) throws RemoteException {
+ IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
+ throws RemoteException {
super(Looper.getMainLooper());
@@ -118,6 +125,7 @@
mVideoCallListenerBinder = new VideoCallListenerBinder();
mConectionServiceVideoProvider.addVideoCallback(mVideoCallListenerBinder);
mCall = call;
+ mCurrentUserProxy = currentUserProxy;
}
/**
@@ -300,13 +308,15 @@
public void onSetCamera(String cameraId, String callingPackage, int callingUid,
int callingPid) {
synchronized (mLock) {
- logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage);
+ logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage +
+ "; callingUid=" + callingUid);
if (!TextUtils.isEmpty(cameraId)) {
if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) {
// Calling app is not permitted to use the camera. Ignore the request and send
// back a call session event indicating the error.
- Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, pid=%d",
+ Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, "
+ + "pid=%d",
callingPackage, callingUid, callingPid);
VideoProviderProxy.this.handleCallSessionEvent(
Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
@@ -528,11 +538,20 @@
* @param context The context.
* @param callingPackage The package name of the caller (i.e. Dialer).
* @param callingUid The UID of the caller.
+ * @param callingPid The PID of the caller.
* @return {@code true} if the calling uid and package can use the camera, {@code false}
* otherwise.
*/
private boolean canUseCamera(Context context, String callingPackage, int callingUid,
int callingPid) {
+
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ UserHandle currentUserHandle = mCurrentUserProxy.getCurrentUserHandle();
+ if (currentUserHandle != null && !currentUserHandle.equals(callingUser)) {
+ Log.w(this, "canUseCamera attempt to user camera by background user.");
+ return false;
+ }
+
try {
context.enforcePermission(Manifest.permission.CAMERA, callingPid, callingUid,
"Camera permission required.");
@@ -548,7 +567,7 @@
return appOpsManager != null && appOpsManager.noteOp(AppOpsManager.OP_CAMERA,
callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED;
} catch (SecurityException se) {
- Log.w(this, "canUserCamera got appOpps Exception " + se.toString());
+ Log.w(this, "canUseCamera got appOpps Exception " + se.toString());
return false;
}
}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index da0f9b1..ee4890f 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.telecom.Connection.VideoProvider;
import android.telecom.InCallService;
import android.telecom.InCallService.VideoCall;
@@ -211,6 +212,31 @@
}
/**
+ * Tests the caller user handle check in {@link VideoCall#setCamera(String)} to ensure a camera
+ * change from a background user is not permitted.
+ */
+ @MediumTest
+ public void testCameraChangeUserFail() throws Exception {
+ // Wait until the callback has been received before performing verification.
+ doAnswer(mVerification).when(mVideoCallCallback).onCallSessionEvent(anyInt());
+
+ // Set a fake user to be the current foreground user.
+ mTelecomSystem.getCallsManager().onUserSwitch(new UserHandle(1000));
+
+ // Make a request to change the camera
+ mVideoCall.setCamera(MockVideoProvider.CAMERA_FRONT);
+ mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ // Capture the session event reported via the callback.
+ ArgumentCaptor<Integer> sessionEventCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mVideoCallCallback, timeout(TEST_TIMEOUT)).onCallSessionEvent(
+ sessionEventCaptor.capture());
+
+ assertEquals(VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
+ sessionEventCaptor.getValue().intValue());
+ }
+
+ /**
* Tests the caller permission check in {@link VideoCall#setCamera(String)} to ensure the
* caller can null out the camera, even if they do not have camera permission.
*/