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