Enforce CREATE_VIRTUAL_DEVICE permission for virtual camera service

Bug: 301023410
Test: atest
Change-Id: Ia3850ef0ace096dcda6c1d2fe8d3c5fd39e0a271
diff --git a/services/camera/virtualcamera/Android.bp b/services/camera/virtualcamera/Android.bp
index 870b9d5..8d4a476 100644
--- a/services/camera/virtualcamera/Android.bp
+++ b/services/camera/virtualcamera/Android.bp
@@ -54,6 +54,7 @@
         "util/EglProgram.cc",
         "util/EglSurfaceTexture.cc",
         "util/EglUtil.cc",
+        "util/Permissions.cc"
     ],
     defaults: [
         "libvirtualcamera_defaults",
diff --git a/services/camera/virtualcamera/VirtualCameraService.cc b/services/camera/virtualcamera/VirtualCameraService.cc
index 08bfff7..370a5a8 100644
--- a/services/camera/virtualcamera/VirtualCameraService.cc
+++ b/services/camera/virtualcamera/VirtualCameraService.cc
@@ -31,6 +31,7 @@
 #include "android/binder_auto_utils.h"
 #include "android/binder_libbinder.h"
 #include "binder/Status.h"
+#include "util/Permissions.h"
 #include "util/Util.h"
 
 using ::android::binder::Status;
@@ -54,6 +55,8 @@
  * enable_test_camera
  * disable_test_camera
 )";
+constexpr char kCreateVirtualDevicePermission[] =
+    "android.permission.CREATE_VIRTUAL_DEVICE";
 
 ndk::ScopedAStatus validateConfiguration(
     const VirtualCameraConfiguration& configuration) {
@@ -79,17 +82,26 @@
 }  // namespace
 
 VirtualCameraService::VirtualCameraService(
-    std::shared_ptr<VirtualCameraProvider> virtualCameraProvider)
-    : mVirtualCameraProvider(virtualCameraProvider) {
+    std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
+    const PermissionsProxy& permissionProxy)
+    : mVirtualCameraProvider(virtualCameraProvider),
+      mPermissionProxy(permissionProxy) {
 }
 
 ndk::ScopedAStatus VirtualCameraService::registerCamera(
     const ::ndk::SpAIBinder& token,
     const VirtualCameraConfiguration& configuration, bool* _aidl_return) {
+  if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
+    ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
+          getpid(), getuid(), kCreateVirtualDevicePermission);
+    return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
+  }
+
   if (_aidl_return == nullptr) {
     return ndk::ScopedAStatus::fromServiceSpecificError(
         Status::EX_ILLEGAL_ARGUMENT);
   }
+
   *_aidl_return = true;
 
   auto status = validateConfiguration(configuration);
@@ -127,6 +139,12 @@
 
 ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
     const ::ndk::SpAIBinder& token) {
+  if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
+    ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
+          getpid(), getuid(), kCreateVirtualDevicePermission);
+    return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
+  }
+
   std::lock_guard lock(mLock);
 
   auto it = mTokenToCameraName.find(token);
@@ -145,6 +163,12 @@
 
 ndk::ScopedAStatus VirtualCameraService::getCameraId(
         const ::ndk::SpAIBinder& token, int32_t* _aidl_return) {
+  if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
+    ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
+          getpid(), getuid(), kCreateVirtualDevicePermission);
+    return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
+  }
+
   if (_aidl_return == nullptr) {
     return ndk::ScopedAStatus::fromServiceSpecificError(
             Status::EX_ILLEGAL_ARGUMENT);
diff --git a/services/camera/virtualcamera/VirtualCameraService.h b/services/camera/virtualcamera/VirtualCameraService.h
index b68d43a..d573986 100644
--- a/services/camera/virtualcamera/VirtualCameraService.h
+++ b/services/camera/virtualcamera/VirtualCameraService.h
@@ -24,6 +24,7 @@
 #include "VirtualCameraDevice.h"
 #include "VirtualCameraProvider.h"
 #include "aidl/android/companion/virtualcamera/BnVirtualCameraService.h"
+#include "util/Permissions.h"
 
 namespace android {
 namespace companion {
@@ -34,7 +35,8 @@
     : public aidl::android::companion::virtualcamera::BnVirtualCameraService {
  public:
   VirtualCameraService(
-      std::shared_ptr<VirtualCameraProvider> virtualCameraProvider);
+      std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
+      const PermissionsProxy& permissionProxy = PermissionsProxy::get());
 
   // Register camera corresponding to the binder token.
   ndk::ScopedAStatus registerCamera(
@@ -68,6 +70,8 @@
 
   std::shared_ptr<VirtualCameraProvider> mVirtualCameraProvider;
 
+  const PermissionsProxy& mPermissionProxy;
+
   std::mutex mLock;
   struct BinderTokenHash {
     std::size_t operator()(const ::ndk::SpAIBinder& key) const {
diff --git a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
index f931cb4..f4d5042 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
@@ -29,6 +29,7 @@
 #include "binder/Binder.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "util/Permissions.h"
 #include "utils/Errors.h"
 
 namespace android {
@@ -50,10 +51,13 @@
 using ::testing::IsEmpty;
 using ::testing::IsNull;
 using ::testing::Not;
+using ::testing::Return;
 using ::testing::SizeIs;
 
 constexpr int kVgaWidth = 640;
 constexpr int kVgaHeight = 480;
+constexpr char kCreateVirtualDevicePermissions[] =
+    "android.permission.CREATE_VIRTUAL_DEVICE";
 
 const VirtualCameraConfiguration kEmptyVirtualCameraConfiguration;
 
@@ -76,6 +80,12 @@
               (override));
 };
 
+class MockPermissionsProxy : public PermissionsProxy {
+ public:
+  MOCK_METHOD(bool, checkCallingPermission, (const std::string&),
+              (const override));
+};
+
 class VirtualCameraServiceTest : public ::testing::Test {
  public:
   void SetUp() override {
@@ -87,8 +97,11 @@
           return ndk::ScopedAStatus::ok();
         });
     mCameraProvider->setCallback(mMockCameraProviderCallback);
-    mCameraService =
-        ndk::SharedRefBase::make<VirtualCameraService>(mCameraProvider);
+    mCameraService = ndk::SharedRefBase::make<VirtualCameraService>(
+        mCameraProvider, mMockPermissionsProxy);
+
+    ON_CALL(mMockPermissionsProxy, checkCallingPermission)
+        .WillByDefault(Return(true));
 
     mDevNullFd = open("/dev/null", O_RDWR);
     ASSERT_THAT(mDevNullFd, Ge(0));
@@ -129,6 +142,7 @@
   std::shared_ptr<VirtualCameraProvider> mCameraProvider;
   std::shared_ptr<MockCameraProviderCallback> mMockCameraProviderCallback =
       ndk::SharedRefBase::make<MockCameraProviderCallback>();
+  MockPermissionsProxy mMockPermissionsProxy;
 
   sp<BBinder> mOwnerToken;
   ndk::SpAIBinder mNdkOwnerToken;
@@ -242,6 +256,40 @@
   EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), IsNull());
 }
 
+TEST_F(VirtualCameraServiceTest, RegisterCameraWithoutPermissionFails) {
+  bool aidlRet;
+  EXPECT_CALL(mMockPermissionsProxy,
+              checkCallingPermission(kCreateVirtualDevicePermissions))
+      .WillOnce(Return(false));
+
+  EXPECT_THAT(mCameraService
+                  ->registerCamera(mNdkOwnerToken, mVgaYUV420OnlyConfiguration,
+                                   &aidlRet)
+                  .getExceptionCode(),
+              Eq(EX_SECURITY));
+}
+
+TEST_F(VirtualCameraServiceTest, UnregisterCameraWithoutPermissionFails) {
+  EXPECT_CALL(mMockPermissionsProxy,
+              checkCallingPermission(kCreateVirtualDevicePermissions))
+      .WillOnce(Return(false));
+
+  EXPECT_THAT(
+      mCameraService->unregisterCamera(mNdkOwnerToken).getExceptionCode(),
+      Eq(EX_SECURITY));
+}
+
+TEST_F(VirtualCameraServiceTest, GetIdWithoutPermissionFails) {
+  int32_t aidlRet;
+  EXPECT_CALL(mMockPermissionsProxy,
+              checkCallingPermission(kCreateVirtualDevicePermissions))
+      .WillOnce(Return(false));
+
+  EXPECT_THAT(
+      mCameraService->getCameraId(mNdkOwnerToken, &aidlRet).getExceptionCode(),
+      Eq(EX_SECURITY));
+}
+
 TEST_F(VirtualCameraServiceTest, UnregisterCameraWithUnknownToken) {
   createCamera();
 
diff --git a/services/camera/virtualcamera/util/Permissions.cc b/services/camera/virtualcamera/util/Permissions.cc
new file mode 100644
index 0000000..634bca3
--- /dev/null
+++ b/services/camera/virtualcamera/util/Permissions.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraPermissions"
+
+#include "Permissions.h"
+
+#include "binder/PermissionCache.h"
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+class PermissionsProxyImpl : public PermissionsProxy {
+ public:
+  bool checkCallingPermission(const std::string& permission) const override;
+};
+
+bool PermissionsProxyImpl::checkCallingPermission(
+    const std::string& permission) const {
+  int32_t uid;
+  int32_t pid;
+  const bool hasPermission = PermissionCache::checkCallingPermission(
+      String16(permission.c_str()), &pid, &uid);
+
+  ALOGV("%s: Checking %s permission for pid %d uid %d: %s", __func__,
+        permission.c_str(), pid, uid, hasPermission ? "granted" : "denied");
+  return hasPermission;
+}
+}  // namespace
+
+const PermissionsProxy& PermissionsProxy::get() {
+  static PermissionsProxyImpl sPermissionProxyImpl;
+  return sPermissionProxyImpl;
+}
+
+}  // namespace virtualcamera
+}  // namespace companion
+}  // namespace android
diff --git a/services/camera/virtualcamera/util/Permissions.h b/services/camera/virtualcamera/util/Permissions.h
new file mode 100644
index 0000000..014989e
--- /dev/null
+++ b/services/camera/virtualcamera/util/Permissions.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_PERMISSIONS_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_PERMISSIONS_H
+
+#include <string>
+
+#include "sys/types.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+class PermissionsProxy {
+ public:
+  virtual ~PermissionsProxy() = default;
+
+  // Checks whether caller holds permission. Do not use with runtime permissions
+  // as the default implementation uses PermissionCache which doesn't reflect
+  // possible runtime changes of permissions.
+  //
+  // Returns true in case caller holds the permission, false otherwise or if
+  // there was any error while verifying the permission.
+  virtual bool checkCallingPermission(const std::string& permission) const = 0;
+
+  // Get instance of PermissionProxy.
+  static const PermissionsProxy& get();
+};
+
+}  // namespace virtualcamera
+}  // namespace companion
+}  // namespace android
+
+#endif  // ANDROID_COMPANION_VIRTUALCAMERA_PERMISSIONS_H