camera2: Enforce client eviction on user switch.

Bug: 19186859
Change-Id: I172a2ce46c8e8a131ae7e8dd99d60c5f4f0d6668
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index c2df489..e9c96c6 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -35,7 +35,6 @@
 #include <binder/ProcessInfoService.h>
 #include <cutils/atomic.h>
 #include <cutils/properties.h>
-#include <cutils/multiuser.h>
 #include <gui/Surface.h>
 #include <hardware/hardware.h>
 #include <media/AudioSystem.h>
@@ -123,9 +122,8 @@
 // should be ok for now.
 static CameraService *gCameraService;
 
-CameraService::CameraService()
-    : mEventLog(DEFAULT_EVICTION_LOG_LENGTH), mSoundRef(0), mModule(0), mFlashlight(0)
-{
+CameraService::CameraService() : mEventLog(DEFAULT_EVICTION_LOG_LENGTH),
+        mLastUserId(DEFAULT_LAST_USER_ID), mSoundRef(0), mModule(0), mFlashlight(0) {
     ALOGI("CameraService started (pid=%d)", getpid());
     gCameraService = this;
 
@@ -757,7 +755,8 @@
     return INVALID_OPERATION;
 }
 
-status_t CameraService::validateConnect(const String8& cameraId, /*inout*/int& clientUid) const {
+status_t CameraService::validateConnectLocked(const String8& cameraId, /*inout*/int& clientUid)
+        const {
 
     int callingPid = getCallingPid();
 
@@ -797,6 +796,13 @@
         return -EACCES;
     }
 
+    // Only allow clients who are being used by the current foreground device user.
+    if (mLastUserId != clientUserId && mLastUserId != DEFAULT_LAST_USER_ID) {
+        ALOGE("CameraService::connect X (PID %d) rejected (cannot connect from non-foreground "
+                "device user)", callingPid);
+        return PERMISSION_DENIED;
+    }
+
     return checkIfDeviceIsUsable(cameraId);
 }
 
@@ -1163,6 +1169,21 @@
     return OK;
 }
 
+void CameraService::notifySystemEvent(int eventId, int arg0) {
+    switch(eventId) {
+        case ICameraService::USER_SWITCHED: {
+            doUserSwitch(/*newUserId*/arg0);
+            break;
+        }
+        case ICameraService::NO_EVENT:
+        default: {
+            ALOGW("%s: Received invalid system event from system_server: %d", __FUNCTION__,
+                    eventId);
+            break;
+        }
+    }
+}
+
 status_t CameraService::addListener(
                                 const sp<ICameraServiceListener>& listener) {
     ALOGV("%s: Add listener %p", __FUNCTION__, listener.get());
@@ -1351,6 +1372,8 @@
         // other clients from connecting in mServiceLockWrapper if held
         mServiceLock.unlock();
 
+        // Do not clear caller identity, remote caller should be client proccess
+
         for (auto& i : evicted) {
             if (i.get() != nullptr) {
                 i->disconnect();
@@ -1392,6 +1415,60 @@
     return clientDescriptorPtr->getValue();
 }
 
+void CameraService::doUserSwitch(int newUserId) {
+    // Acquire mServiceLock and prevent other clients from connecting
+    std::unique_ptr<AutoConditionLock> lock =
+            AutoConditionLock::waitAndAcquire(mServiceLockWrapper);
+
+    if (newUserId <= 0) {
+        ALOGW("%s: Bad user ID %d given during user switch, resetting to default.", __FUNCTION__,
+                newUserId);
+        newUserId = DEFAULT_LAST_USER_ID;
+    }
+
+    mLastUserId = newUserId;
+
+    // Current user has switched, evict all current clients.
+    std::vector<sp<BasicClient>> evicted;
+    for (auto& i : mActiveClientManager.getAll()) {
+        auto clientSp = i->getValue();
+
+        if (clientSp.get() == nullptr) {
+            ALOGE("%s: Dead client still in mActiveClientManager.", __FUNCTION__);
+            continue;
+        }
+
+        evicted.push_back(clientSp);
+
+        String8 curTime = getFormattedCurrentTime();
+
+        ALOGE("Evicting conflicting client for camera ID %s due to user change",
+                i->getKey().string());
+        // Log the clients evicted
+        mEventLog.add(String8::format("%s : EVICT device %s client for package %s (PID %"
+                PRId32 ", priority %" PRId32 ")\n   - Evicted due to user switch.",
+                curTime.string(), i->getKey().string(),
+                String8{clientSp->getPackageName()}.string(), i->getOwnerId(),
+                i->getPriority()));
+
+    }
+
+    // Do not hold mServiceLock while disconnecting clients, but retain the condition
+    // blocking other clients from connecting in mServiceLockWrapper if held.
+    mServiceLock.unlock();
+
+    // Clear caller identity temporarily so client disconnect PID checks work correctly
+    int64_t token = IPCThreadState::self()->clearCallingIdentity();
+
+    for (auto& i : evicted) {
+        i->disconnect();
+    }
+
+    IPCThreadState::self()->restoreCallingIdentity(token);
+
+    // Reacquire mServiceLock
+    mServiceLock.lock();
+}
 
 void CameraService::logDisconnected(const String8& cameraId, int clientPid,
         const String8& clientPackage) {
@@ -1411,16 +1488,18 @@
             curTime.string(), cameraId.string(), clientPackage.string(), clientPid));
 }
 
-status_t CameraService::onTransact(
-    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+status_t CameraService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+        uint32_t flags) {
+
+    const int pid = getCallingPid();
+    const int selfPid = getpid();
+
     // Permission checks
     switch (code) {
         case BnCameraService::CONNECT:
         case BnCameraService::CONNECT_DEVICE:
-        case BnCameraService::CONNECT_LEGACY:
-            const int pid = getCallingPid();
-            const int self_pid = getpid();
-            if (pid != self_pid) {
+        case BnCameraService::CONNECT_LEGACY: {
+            if (pid != selfPid) {
                 // we're called from a different process, do the real check
                 if (!checkCallingPermission(
                         String16("android.permission.CAMERA"))) {
@@ -1431,6 +1510,21 @@
                 }
             }
             break;
+        }
+        case BnCameraService::NOTIFY_SYSTEM_EVENT: {
+            if (pid != selfPid) {
+                // Ensure we're being called by system_server, or similar process with
+                // permissions to notify the camera service about system events
+                if (!checkCallingPermission(
+                        String16("android.permission.CAMERA_SEND_SYSTEM_EVENTS"))) {
+                    const int uid = getCallingUid();
+                    ALOGE("Permission Denial: cannot send updates to camera service about system"
+                            " events from pid=%d, uid=%d", pid, uid);
+                    return PERMISSION_DENIED;
+                }
+            }
+            break;
+        }
     }
 
     return BnCameraService::onTransact(code, data, reply, flags);
@@ -1544,7 +1638,11 @@
 }
 
 void CameraService::BasicClient::disconnect() {
-    if (mDisconnected) return;
+    if (mDisconnected) {
+        ALOGE("%s: Disconnect called on already disconnected client for device %d", __FUNCTION__,
+                mCameraId);
+        return;
+    }
     mDisconnected = true;;
 
     mCameraService->removeByClient(this);
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 53f1c72..ca1c504 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SERVERS_CAMERA_CAMERASERVICE_H
 #define ANDROID_SERVERS_CAMERA_CAMERASERVICE_H
 
+#include <cutils/multiuser.h>
 #include <utils/Vector.h>
 #include <utils/KeyedVector.h>
 #include <binder/AppOpsManager.h>
@@ -92,6 +93,11 @@
     // Default number of messages to store in eviction log
     static const size_t DEFAULT_EVICTION_LOG_LENGTH = 50;
 
+    enum {
+        // Default last user id
+        DEFAULT_LAST_USER_ID = 0,
+    };
+
     // Implementation of BinderService<T>
     static char const* getServiceName() { return "media.camera"; }
 
@@ -145,6 +151,8 @@
     virtual status_t    setTorchMode(const String16& cameraId, bool enabled,
             const sp<IBinder>& clientBinder);
 
+    virtual void notifySystemEvent(int eventId, int arg0);
+
     // OK = supports api of that version, -EOPNOTSUPP = does not support
     virtual status_t    supportsCameraApi(
             int cameraId, int apiVersion);
@@ -447,7 +455,7 @@
     virtual void onFirstRef();
 
     // Check if we can connect, before we acquire the service lock.
-    status_t validateConnect(const String8& cameraId, /*inout*/int& clientUid) const;
+    status_t validateConnectLocked(const String8& cameraId, /*inout*/int& clientUid) const;
 
     // Handle active client evictions, and update service state.
     // Only call with with mServiceLock held.
@@ -485,6 +493,9 @@
     // Circular buffer for storing event logging for dumps
     RingBuffer<String8> mEventLog;
 
+    // UID of last user.
+    int mLastUserId;
+
     /**
      * Get the camera state for a given camera id.
      *
@@ -530,6 +541,11 @@
     sp<CameraService::BasicClient> removeClientLocked(const String8& cameraId);
 
     /**
+     * Handle a notification that the current device user has changed.
+     */
+    void doUserSwitch(int newUserId);
+
+    /**
      * Add a event log message that a client has been disconnected.
      */
     void logDisconnected(const String8& cameraId, int clientPid, const String8& clientPackage);
@@ -702,22 +718,24 @@
             "Camera API version %d", clientPid, clientName8.string(), cameraId.string(),
             halVersion, static_cast<int>(effectiveApiLevel));
 
-    // Enforce client permissions and do basic sanity checks
-    if((ret = validateConnect(cameraId, /*inout*/clientUid)) != NO_ERROR) {
-        return ret;
-    }
-
     sp<CLIENT> client = nullptr;
     {
         // Acquire mServiceLock and prevent other clients from connecting
         std::unique_ptr<AutoConditionLock> lock =
                 AutoConditionLock::waitAndAcquire(mServiceLockWrapper, DEFAULT_CONNECT_TIMEOUT_NS);
+
         if (lock == nullptr) {
             ALOGE("CameraService::connect X (PID %d) rejected (too many other clients connecting)."
                     , clientPid);
             return -EBUSY;
         }
 
+        // Enforce client permissions and do basic sanity checks
+        if((ret = validateConnectLocked(cameraId, /*inout*/clientUid)) != NO_ERROR) {
+            return ret;
+        }
+        mLastUserId = multiuser_get_user_id(clientUid);
+
         // Check the shim parameters after acquiring lock, if they have already been updated and
         // we were doing a shim update, return immediately
         if (shimUpdateOnly) {