Add ValidatedAttributionSourceState
Add type which encapsulates a validated attribution source, either via
binder context, or if it is passed from a trusted source.
This type will be used by the controller for permission validation.
Test: atest audiopermissioncontroller_test --host
Fixes: 259493676
Bug: 338089555
Flag: EXEMPT safe, adding utilities
Change-Id: I80af36b4b766b7c876f3d7cdb50257dc4d1c7dcd
diff --git a/services/audiopolicy/permission/Android.bp b/services/audiopolicy/permission/Android.bp
index 1aa29e0..ce7b43c 100644
--- a/services/audiopolicy/permission/Android.bp
+++ b/services/audiopolicy/permission/Android.bp
@@ -8,6 +8,7 @@
srcs: [
"NativePermissionController.cpp",
+ "ValidatedAttributionSourceState.cpp",
],
export_include_dirs: [
"include",
@@ -22,6 +23,7 @@
],
static_libs: [
"audio-permission-aidl-cpp",
+ "framework-permission-aidl-cpp",
],
shared_libs: [
"libbase",
@@ -94,6 +96,7 @@
],
srcs: [
"tests/NativePermissionControllerTest.cpp",
+ "tests/ValidatedAttributionSourceStateTest.cpp",
],
test_options: {
unit_test: true,
diff --git a/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp b/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp
new file mode 100644
index 0000000..2c32289
--- /dev/null
+++ b/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <media/ValidatedAttributionSourceState.h>
+
+#include <binder/IPCThreadState.h>
+#include <error/expected_utils.h>
+#include <utils/Log.h>
+
+namespace com::android::media::permission {
+
+using ::android::base::unexpected;
+
+Result<ValidatedAttributionSourceState> ValidatedAttributionSourceState::createFromBinderContext(
+ AttributionSourceState attr, const IPermissionProvider& provider) {
+ attr.pid = ::android::IPCThreadState::self()->getCallingPid();
+ attr.uid = ::android::IPCThreadState::self()->getCallingUid();
+ return createFromTrustedUidNoPackage(std::move(attr), provider);
+}
+
+Result<ValidatedAttributionSourceState>
+ValidatedAttributionSourceState::createFromTrustedUidNoPackage(
+ AttributionSourceState attr, const IPermissionProvider& provider) {
+ if (attr.packageName.has_value() && attr.packageName->size() != 0) {
+ if (VALUE_OR_RETURN(provider.validateUidPackagePair(attr.uid, attr.packageName.value()))) {
+ return ValidatedAttributionSourceState{std::move(attr)};
+ } else {
+ return unexpected{::android::PERMISSION_DENIED};
+ }
+ } else {
+ // For APIs which don't appropriately pass attribution sources or packages, we need
+ // to populate the package name with our best guess.
+ const auto packageNames = VALUE_OR_RETURN(provider.getPackagesForUid(attr.uid));
+ LOG_ALWAYS_FATAL_IF(packageNames.empty());
+ attr.packageName = std::move(packageNames[0]);
+ return ValidatedAttributionSourceState{std::move(attr)};
+ }
+}
+
+} // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h b/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h
new file mode 100644
index 0000000..8d9da05
--- /dev/null
+++ b/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include <android/content/AttributionSourceState.h>
+#include <error/Result.h>
+
+#include "IPermissionProvider.h"
+
+namespace com::android::media::permission {
+
+using ::android::content::AttributionSourceState;
+using ::android::error::Result;
+
+class ValidatedAttributionSourceState {
+ public:
+ /**
+ * Validates an attribution source from within the context of a binder transaction.
+ * Overwrites the uid/pid and validates the packageName
+ */
+ static Result<ValidatedAttributionSourceState> createFromBinderContext(
+ AttributionSourceState attr, const IPermissionProvider& provider);
+
+ /**
+ * Creates a ValidatedAttributionSourceState in cases where the source is passed from a
+ * trusted entity which already performed validation.
+ */
+ static ValidatedAttributionSourceState createFromTrustedSource(AttributionSourceState attr) {
+ return ValidatedAttributionSourceState(attr);
+ }
+
+ /**
+ * Create a ValidatedAttribubtionSourceState in cases where the uid/pid is trusted, but the
+ * packages have not been validated. Proper use of the previous two methods should avoid the
+ * necessity of this, but it is useful for migration purposes as well as testing this class.
+ */
+ static Result<ValidatedAttributionSourceState> createFromTrustedUidNoPackage(
+ AttributionSourceState attr, const IPermissionProvider& provider);
+
+ operator AttributionSourceState() const { return state_; }
+
+ operator const AttributionSourceState&() const { return state_; }
+
+ AttributionSourceState unwrapInto() && { return std::move(state_); }
+
+ bool operator==(const ValidatedAttributionSourceState& other) const {
+ return operator==(other.state_);
+ }
+
+ bool operator==(const AttributionSourceState& other) const { return state_ == other; }
+
+ private:
+ ValidatedAttributionSourceState(AttributionSourceState attr) : state_(attr) {}
+
+ AttributionSourceState state_;
+};
+} // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
new file mode 100644
index 0000000..f4d6556
--- /dev/null
+++ b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <media/ValidatedAttributionSourceState.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/expected.h>
+#include <media/IPermissionProvider.h>
+
+using ::android::base::unexpected;
+using ::android::binder::Status;
+using ::android::content::AttributionSourceState;
+using ::android::error::Result;
+using ::com::android::media::permission::IPermissionProvider;
+using ::com::android::media::permission::ValidatedAttributionSourceState;
+using ::testing::Return;
+
+class MockPermissionProvider : public IPermissionProvider {
+ public:
+ MOCK_METHOD(Result<std::vector<std::string>>, getPackagesForUid, (uid_t uid),
+ (override, const));
+ MOCK_METHOD(Result<bool>, validateUidPackagePair, (uid_t uid, const std::string&),
+ (override, const));
+};
+
+class ValidatedAttributionSourceStateTest : public ::testing::Test {
+ protected:
+ MockPermissionProvider mMockProvider;
+ const uid_t mUid = 10001;
+ const std::vector<std::string> mPackageList{"com.package1", "com.package2"};
+};
+
+#define UNWRAP_EQ(expr, desired_expr) \
+ do { \
+ auto tmp_ = (expr); \
+ EXPECT_TRUE(tmp_.has_value()); \
+ if (tmp_.has_value()) EXPECT_EQ(*tmp_, desired_expr); \
+ } while (0)
+
+TEST_F(ValidatedAttributionSourceStateTest, providedPackageValid) {
+ const std::string package = "com.package1";
+ EXPECT_CALL(mMockProvider, validateUidPackagePair(mUid, package)).WillOnce(Return(true));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ attr.packageName = package;
+ UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+ attr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, providedPackageInvalid) {
+ const std::string package = "com.package.spoof";
+ EXPECT_CALL(mMockProvider, validateUidPackagePair(mUid, package)).WillOnce(Return(false));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ attr.packageName = package;
+ const auto res =
+ ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), ::android::PERMISSION_DENIED);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, packageLookup_whenMissingPackage) {
+ EXPECT_CALL(mMockProvider, getPackagesForUid(mUid)).WillOnce(Return(mPackageList));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ AttributionSourceState expectedAttr;
+ expectedAttr.uid = mUid;
+ expectedAttr.packageName = "com.package1";
+ UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+ expectedAttr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, packageLookup_whenEmptyPackage) {
+ EXPECT_CALL(mMockProvider, getPackagesForUid(mUid)).WillOnce(Return(mPackageList));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ attr.packageName = std::string{};
+ AttributionSourceState expectedAttr;
+ expectedAttr.uid = mUid;
+ expectedAttr.packageName = "com.package1";
+ UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+ expectedAttr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, controllerNotInitialized) {
+ EXPECT_CALL(mMockProvider, getPackagesForUid(mUid))
+ .WillOnce(Return(unexpected{::android::NO_INIT}));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ attr.packageName = std::string{};
+ AttributionSourceState expectedAttr;
+ expectedAttr.uid = mUid;
+ expectedAttr.packageName = "com.package1";
+ const auto res =
+ ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), ::android::NO_INIT);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, uidNotFound) {
+ EXPECT_CALL(mMockProvider, getPackagesForUid(mUid))
+ .WillOnce(Return(unexpected{::android::BAD_VALUE}));
+ AttributionSourceState attr;
+ attr.uid = mUid;
+ attr.packageName = std::string{};
+ const auto res =
+ ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), ::android::BAD_VALUE);
+}