diff --git a/vehicle/2.0/default/Android.mk b/vehicle/2.0/default/Android.mk index 46733e5d68..e61aaa33c5 100644 --- a/vehicle/2.0/default/Android.mk +++ b/vehicle/2.0/default/Android.mk @@ -22,6 +22,7 @@ module_prefix = android.hardware.vehicle@2.0 include $(CLEAR_VARS) LOCAL_MODULE := $(module_prefix)-manager-lib LOCAL_SRC_FILES := \ + vehicle_hal_manager/AccessControlConfigParser.cpp \ vehicle_hal_manager/SubscriptionManager.cpp \ vehicle_hal_manager/VehicleHalManager.cpp \ @@ -67,6 +68,7 @@ LOCAL_MODULE:= $(module_prefix)-manager-unit-tests LOCAL_WHOLE_STATIC_LIBRARIES := $(module_prefix)-manager-lib LOCAL_SRC_FILES:= \ + tests/AccessControlConfigParser_test.cpp \ tests/VehicleObjectPool_test.cpp \ tests/VehiclePropConfigIndex_test.cpp \ tests/SubscriptionManager_test.cpp \ diff --git a/vehicle/2.0/default/tests/AccessControlConfigParser_test.cpp b/vehicle/2.0/default/tests/AccessControlConfigParser_test.cpp new file mode 100644 index 0000000000..92d7e39d6e --- /dev/null +++ b/vehicle/2.0/default/tests/AccessControlConfigParser_test.cpp @@ -0,0 +1,149 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "vehicle_hal_manager/AccessControlConfigParser.h" + +namespace android { +namespace hardware { +namespace vehicle { +namespace V2_0 { + +namespace { + +class AccessControlConfigParserTest : public ::testing::Test { +protected: + void SetUp() override { + std::vector supportedProperties { + VehicleProperty::HVAC_FAN_SPEED, + VehicleProperty::HVAC_FAN_DIRECTION, + }; + parser.reset(new AccessControlConfigParser(supportedProperties)); + } +public: + PropertyAclMap aclMap; + std::unique_ptr parser; +}; + +TEST_F(AccessControlConfigParserTest, basicParsing) { + std::stringstream file; + file << "S:0x0500 1000 RW" << std::endl; + + ASSERT_TRUE(parser->parseFromStream(&file, &aclMap)); + + ASSERT_EQ(1, aclMap.size()); + auto it = aclMap.find(VehicleProperty::HVAC_FAN_SPEED); + ASSERT_NE(aclMap.end(), it); + ASSERT_EQ(VehiclePropertyAccess::READ_WRITE, it->second.access); + ASSERT_EQ(VehicleProperty::HVAC_FAN_SPEED, it->second.propId); + ASSERT_EQ(1000u, it->second.uid); +} + +TEST_F(AccessControlConfigParserTest, multipleUids) { + std::stringstream file; + file << "Set AID_AUDIO 1004" << std::endl + << "Set AID_SYSTEM 1000" << std::endl + << "S:0x0500 AID_SYSTEM RW" << std::endl + << "S:0x0500 AID_AUDIO RW" << std::endl + << "S:0x0500 0xbeef R" << std::endl; // Read-only. + + std::unordered_set expectedUids {1000, 1004, 0xbeef}; + + ASSERT_TRUE(parser->parseFromStream(&file, &aclMap)); + + auto range = aclMap.equal_range(VehicleProperty::HVAC_FAN_SPEED); + for (auto it = range.first; it != range.second; ++it) { + auto& acl = it->second; + + ASSERT_EQ(1, expectedUids.count(acl.uid)) + << " uid: " << std::hex << acl.uid; + + if (acl.uid == 0xbeef) { + ASSERT_EQ(VehiclePropertyAccess::READ, acl.access); + } else { + ASSERT_EQ(VehiclePropertyAccess::READ_WRITE, acl.access); + } + } +} + +TEST_F(AccessControlConfigParserTest, fileContainsJunk) { + std::stringstream file; + file << "This string will be ignored with warning in the log" << std::endl + << "# However comments are quit legitimate" << std::endl + << "S:0x0500 0xbeef R # YAY" << std::endl; + + ASSERT_FALSE(parser->parseFromStream(&file, &aclMap)); + + ASSERT_EQ(1, aclMap.size()); + auto it = aclMap.find(VehicleProperty::HVAC_FAN_SPEED); + ASSERT_NE(aclMap.end(), it); + ASSERT_EQ(VehiclePropertyAccess::READ, it->second.access); + ASSERT_EQ(VehicleProperty::HVAC_FAN_SPEED, it->second.propId); + ASSERT_EQ(0xbeef, it->second.uid); +} + +TEST_F(AccessControlConfigParserTest, badIntegerFormat) { + std::stringstream file; + file << "S:0x0500 A12 RW " << std::endl; + + ASSERT_FALSE(parser->parseFromStream(&file, &aclMap)); + ASSERT_EQ(0, aclMap.size()); +} + +TEST_F(AccessControlConfigParserTest, ignoreNotSupportedProperties) { + std::stringstream file; + file << "S:0x0666 1000 RW " << std::endl; + + ASSERT_FALSE(parser->parseFromStream(&file, &aclMap)); + ASSERT_EQ(0, aclMap.size()); +} + +TEST_F(AccessControlConfigParserTest, multipleCalls) { + std::stringstream configFile; + configFile << "S:0x0500 1000 RW" << std::endl; + + ASSERT_TRUE(parser->parseFromStream(&configFile, &aclMap)); + ASSERT_EQ(1, aclMap.size()); + + std::stringstream configFile2; + configFile2 << "S:0x0501 1004 RW" << std::endl; + ASSERT_TRUE(parser->parseFromStream(&configFile2, &aclMap)); + ASSERT_EQ(2, aclMap.size()); + + auto it = aclMap.find(VehicleProperty::HVAC_FAN_SPEED); + ASSERT_NE(aclMap.end(), it); + ASSERT_EQ(VehiclePropertyAccess::READ_WRITE, it->second.access); + ASSERT_EQ(VehicleProperty::HVAC_FAN_SPEED, it->second.propId); + ASSERT_EQ(1000u, it->second.uid); + + it = aclMap.find(VehicleProperty::HVAC_FAN_DIRECTION); + ASSERT_NE(aclMap.end(), it); + ASSERT_EQ(VehiclePropertyAccess::READ_WRITE, it->second.access); + ASSERT_EQ(VehicleProperty::HVAC_FAN_DIRECTION, it->second.propId); + ASSERT_EQ(1004u, it->second.uid); +} + + +} // namespace anonymous + +} // namespace V2_0 +} // namespace vehicle +} // namespace hardware +} // namespace android diff --git a/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.cpp b/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.cpp new file mode 100644 index 0000000000..1d436c507c --- /dev/null +++ b/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.cpp @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#define LOG_TAG "android.hardware.vehicle@2.0-impl" +#include + +#include +#include +#include + +#include "AccessControlConfigParser.h" + +namespace android { +namespace hardware { +namespace vehicle { +namespace V2_0 { + +AccessControlConfigParser::AccessControlConfigParser( + const std::vector& properties) { + // Property Id in the config file doesn't include information about + // type and area. So we want to create a map from these kind of + // *stripped* properties to the whole VehicleProperty. + // We also want to filter out ACL to the properties that supported + // by concrete Vehicle HAL implementation. + for (VehicleProperty vehicleProperty : properties) { + auto numProp = static_cast(vehicleProperty); + numProp &= ~static_cast(VehiclePropertyType::MASK) + & ~static_cast(VehicleArea::MASK); + mStrippedToVehiclePropertyMap.emplace(numProp, vehicleProperty); + } +} + +bool AccessControlConfigParser::parseFromStream( + std::istream* stream, PropertyAclMap* propertyAclMap) { + std::list tokens; + std::string line; + int lineNo = 0; + bool warnings = false; + for (;std::getline(*stream, line); lineNo++) { + split(line, &tokens); + if (!processTokens(&tokens, propertyAclMap)) { + warnings = true; + ALOGW("Failed to parse line %d : %s", lineNo, line.c_str()); + } + } + return !warnings; +} + + +bool AccessControlConfigParser::processTokens(std::list* tokens, + PropertyAclMap* propertyAclMap) { + std::string token = readNextToken(tokens); + if (token.empty() || token[0] == '#') { // Ignore comment. + return true; + } + + if (token == "Set") { + std::string alias = readNextToken(tokens); + std::string strUid = readNextToken(tokens); + if (alias.empty() || strUid.empty()) { + ALOGW("Expected alias and UID must be specified"); + return false; + } + int uid; + if (!parseInt(strUid.c_str(), &uid)) { + ALOGW("Invalid UID: %d", uid); + } + mUidMap.emplace(std::move(alias), uid); + } else if (token.size() > 2 && token[1] == ':') { + VehiclePropertyGroup propGroup; + if (!parsePropertyGroup(token[0], &propGroup)) { + return false; + } + std::string strUid = readNextToken(tokens); + std::string strAccess = readNextToken(tokens); + if (strUid.empty() || strAccess.empty()) { + ALOGW("Expected UID and access for property: %s", + token.c_str()); + } + + + PropertyAcl acl; + if (parsePropertyId(token.substr(2), propGroup, &acl.propId) + && parseUid(strUid, &acl.uid) + && parseAccess(strAccess, &acl.access)) { + propertyAclMap->emplace(acl.propId, std::move(acl)); + } else { + return false; + } + } else { + ALOGW("Unexpected token: %s", token.c_str()); + return false; + } + + return true; +} + +bool AccessControlConfigParser::parsePropertyGroup( + char group, VehiclePropertyGroup* outPropertyGroup) const { + switch (group) { + case 'S': // Fall through. + case 's': + *outPropertyGroup = VehiclePropertyGroup::SYSTEM; + break; + case 'V': // Fall through. + case 'v': + *outPropertyGroup = VehiclePropertyGroup::VENDOR; + break; + default: + ALOGW("Unexpected group: %c", group); + return false; + } + return true; +} + +bool AccessControlConfigParser::parsePropertyId( + const std::string& strPropId, + VehiclePropertyGroup propertyGroup, + VehicleProperty* outVehicleProperty) const { + int propId; + if (!parseInt(strPropId.c_str(), &propId)) { + ALOGW("Failed to convert property id to integer: %s", + strPropId.c_str()); + return false; + } + propId |= static_cast(propertyGroup); + auto it = mStrippedToVehiclePropertyMap.find(propId); + if (it == mStrippedToVehiclePropertyMap.end()) { + ALOGW("Property Id not found or not supported: 0x%x", propId); + return false; + } + *outVehicleProperty = it->second; + return true; +} + +bool AccessControlConfigParser::parseInt(const char* strValue, + int* outIntValue) { + char* end; + long num = std::strtol(strValue, &end, 0 /* auto detect base */); + bool success = *end == 0 && errno != ERANGE; + if (success) { + *outIntValue = static_cast(num); + } + + return success; +} + +bool AccessControlConfigParser::parseUid(const std::string& strUid, + unsigned* outUid) const { + auto element = mUidMap.find(strUid); + if (element != mUidMap.end()) { + *outUid = element->second; + } else { + int val; + if (!parseInt(strUid.c_str(), &val)) { + ALOGW("Failed to convert UID '%s' to integer", strUid.c_str()); + return false; + } + *outUid = static_cast(val); + } + return true; +} + +bool AccessControlConfigParser::parseAccess( + const std::string& strAccess, VehiclePropertyAccess* outAccess) const { + if (strAccess.size() == 0 || strAccess.size() > 2) { + ALOGW("Unknown access mode '%s'", strAccess.c_str()); + return false; + } + int32_t access = static_cast(VehiclePropertyAccess::NONE); + for (char c : strAccess) { + if (c == 'R' || c == 'r') { + access |= VehiclePropertyAccess::READ; + } else if (c == 'W' || c == 'w') { + access |= VehiclePropertyAccess::WRITE; + } else { + ALOGW("Unknown access mode: %c", c); + return false; + } + } + *outAccess = static_cast(access); + return true; +} + +void AccessControlConfigParser::split(const std::string& line, + std::list* outTokens) { + outTokens->clear(); + std::istringstream iss(line); + + while (!iss.eof()) { + std::string token; + iss >> token; + outTokens->push_back(std::move(token)); + } +} + +std::string AccessControlConfigParser::readNextToken( + std::list* tokens) const { + if (tokens->empty()) { + return ""; + } + + std::string token = tokens->front(); + tokens->pop_front(); + return token; +} + +} // namespace V2_0 +} // namespace vehicle +} // namespace hardware +} // namespace android diff --git a/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.h b/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.h new file mode 100644 index 0000000000..17cbbd643b --- /dev/null +++ b/vehicle/2.0/default/vehicle_hal_manager/AccessControlConfigParser.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#ifndef android_hardware_vehicle_V2_0_AccessControlConfigParser_H_ +#define android_hardware_vehicle_V2_0_AccessControlConfigParser_H_ + +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace vehicle { +namespace V2_0 { + +struct PropertyAcl { + VehicleProperty propId; + unsigned uid; + VehiclePropertyAccess access; +}; + +using PropertyAclMap = std::unordered_multimap; + +/** + * Parser for per-property access control in vehicle HAL. + * + * It supports the following format: + * Set ALIAS_NAME UID + * {S,V}:0x0305 {ALIAS_NAME,UID} {R,W,RW} + * + * ALIAS_NAME is just an alias for UID + * S - for system properties (VehiclePropertyGroup::SYSTEM) + * V - for vendor properties (VehiclePropertyGroup::VENDOR) + * + * Example: + * + * Set AID_AUDIO 1004 + * Set AID_MY_APP 10022 + * + * S:0x0305 AID_AUDIO RW + * S:0x0305 10021 R + * V:0x0101 AID_MY_APP R + */ +class AccessControlConfigParser { +public: + /** + * Creates an instance of AccessControlConfigParser + * + * @param properties - properties supported by HAL implementation + */ + AccessControlConfigParser(const std::vector& properties); + + /** + * Parses config content from given stream and writes results to + * propertyAclMap. + */ + bool parseFromStream(std::istream* stream, PropertyAclMap* propertyAclMap); + +private: + bool processTokens(std::list* tokens, + PropertyAclMap* propertyAclMap); + + bool parsePropertyGroup(char group, + VehiclePropertyGroup* outPropertyGroup) const; + + bool parsePropertyId(const std::string& strPropId, + VehiclePropertyGroup propertyGroup, + VehicleProperty* outVehicleProperty) const; + + bool parseUid(const std::string& strUid, unsigned* outUid) const; + + bool parseAccess(const std::string& strAccess, + VehiclePropertyAccess* outAccess) const; + + + std::string readNextToken(std::list* tokens) const; + + static bool parseInt(const char* strValue, int* outIntValue); + static void split(const std::string& line, + std::list* outTokens); + +private: + std::unordered_map mUidMap {}; // Contains UID + // aliases. + + // Map property ids w/o TYPE and AREA to VehicleProperty. + std::unordered_map mStrippedToVehiclePropertyMap; +}; + +} // namespace V2_0 +} // namespace vehicle +} // namespace hardware +} // namespace android + +#endif // android_hardware_vehicle_V2_0_AccessControlConfigParser_H_ diff --git a/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.cpp b/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.cpp index 1260f20176..267c515e33 100644 --- a/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.cpp +++ b/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "VehicleHalManager.h" @@ -52,10 +54,8 @@ Return VehicleHalManager::getAllPropConfigs( const_cast(halConfig.data()), halConfig.size()); - ALOGI("getAllPropConfigs calling callback"); _hidl_cb(hidlConfigs); - ALOGI("getAllPropConfigs done"); return Void(); } @@ -88,7 +88,7 @@ Return VehicleHalManager::get( return Void(); } - if (!checkReadPermission(*config, getCallee())) { + if (!checkReadPermission(*config, getCaller())) { _hidl_cb(StatusCode::INVALID_ARG, kEmptyValue); return Void(); } @@ -109,7 +109,7 @@ Return VehicleHalManager::set(const VehiclePropValue &value) { return StatusCode::INVALID_ARG; } - if (!checkWritePermission(*config, getCallee())) { + if (!checkWritePermission(*config, getCaller())) { return StatusCode::INVALID_ARG; } @@ -194,7 +194,21 @@ void VehicleHalManager::init() { _1, _2, _3)); // Initialize index with vehicle configurations received from VehicleHal. - mConfigIndex.reset(new VehiclePropConfigIndex(mHal->listProperties())); + auto supportedPropConfigs = mHal->listProperties(); + mConfigIndex.reset(new VehiclePropConfigIndex(supportedPropConfigs)); + + std::vector supportedProperties( + supportedPropConfigs.size()); + for (const auto& config : supportedPropConfigs) { + supportedProperties.push_back(config.prop); + } + + AccessControlConfigParser aclParser(supportedProperties); + const char* configs[] = { "/system/etc/vehicle_access.conf", + "/vendor/etc/vehicle_access.conf" }; + for (const char* filename : configs) { + readAndParseAclConfig(filename, &aclParser, &mPropertyAclMap); + } } VehicleHalManager::~VehicleHalManager() { @@ -292,24 +306,43 @@ bool VehicleHalManager::isSubscribable(const VehiclePropConfig& config, return true; } +bool checkAcl(const PropertyAclMap& aclMap, + uid_t callerUid, + VehicleProperty propertyId, + VehiclePropertyAccess requiredAccess) { + if (callerUid == AID_SYSTEM && isSystemProperty(propertyId)) { + return true; + } + + auto range = aclMap.equal_range(propertyId); + for (auto it = range.first; it != range.second; ++it) { + auto& acl = it->second; + if (acl.uid == callerUid && (acl.access & requiredAccess)) { + return true; + } + } + return false; +} + bool VehicleHalManager::checkWritePermission(const VehiclePropConfig &config, - const Callee& callee) { + const Caller& caller) const { if (!(config.access & VehiclePropertyAccess::WRITE)) { ALOGW("Property 0%x has no write access", config.prop); return false; } - //TODO(pavelm): check pid/uid has write access - return true; + return checkAcl(mPropertyAclMap, caller.uid, config.prop, + VehiclePropertyAccess::WRITE); } bool VehicleHalManager::checkReadPermission(const VehiclePropConfig &config, - const Callee& callee) { + const Caller& caller) const { if (!(config.access & VehiclePropertyAccess::READ)) { ALOGW("Property 0%x has no read access", config.prop); return false; } - //TODO(pavelm): check pid/uid has read access - return true; + + return checkAcl(mPropertyAclMap, caller.uid, config.prop, + VehiclePropertyAccess::READ); } void VehicleHalManager::handlePropertySetEvent(const VehiclePropValue& value) { @@ -326,13 +359,24 @@ const VehiclePropConfig* VehicleHalManager::getPropConfigOrNull( ? &mConfigIndex->getConfig(prop) : nullptr; } -Callee VehicleHalManager::getCallee() { - Callee callee; +Caller VehicleHalManager::getCaller() { + Caller caller; IPCThreadState* self = IPCThreadState::self(); - callee.pid = self->getCallingPid(); - callee.uid = self->getCallingUid(); + caller.pid = self->getCallingPid(); + caller.uid = self->getCallingUid(); - return callee; + return caller; +} + +void VehicleHalManager::readAndParseAclConfig(const char* filename, + AccessControlConfigParser* parser, + PropertyAclMap* outAclMap) { + std::ifstream file(filename); + if (file.is_open()) { + ALOGI("Parsing file: %s", filename); + parser->parseFromStream(&file, outAclMap); + file.close(); + } } } // namespace V2_0 diff --git a/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.h b/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.h index 8353679807..cb846c9ecc 100644 --- a/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.h +++ b/vehicle/2.0/default/vehicle_hal_manager/VehicleHalManager.h @@ -29,10 +29,11 @@ #include -#include "VehicleHal.h" -#include "VehiclePropConfigIndex.h" +#include "AccessControlConfigParser.h" #include "ConcurrentQueue.h" #include "SubscriptionManager.h" +#include "VehicleHal.h" +#include "VehiclePropConfigIndex.h" #include "VehicleObjectPool.h" namespace android { @@ -40,7 +41,7 @@ namespace hardware { namespace vehicle { namespace V2_0 { -struct Callee { +struct Caller { pid_t pid; uid_t uid; }; @@ -95,17 +96,21 @@ private: const VehiclePropConfig* getPropConfigOrNull(VehicleProperty prop) const; + bool checkWritePermission(const VehiclePropConfig &config, + const Caller& callee) const; + bool checkReadPermission(const VehiclePropConfig &config, + const Caller& caller) const; + static bool isSubscribable(const VehiclePropConfig& config, SubscribeFlags flags); static bool isSampleRateFixed(VehiclePropertyChangeMode mode); static float checkSampleRate(const VehiclePropConfig& config, float sampleRate); - static bool checkWritePermission(const VehiclePropConfig &config, - const Callee& callee); - static bool checkReadPermission(const VehiclePropConfig &config, - const Callee& callee); + static void readAndParseAclConfig(const char* filename, + AccessControlConfigParser* parser, + PropertyAclMap* outAclMap); - static Callee getCallee(); + static Caller getCaller(); private: VehicleHal* mHal; @@ -117,6 +122,7 @@ private: ConcurrentQueue mEventQueue; BatchingConsumer mBatchingConsumer; VehiclePropValuePool mValueObjectPool; + PropertyAclMap mPropertyAclMap; }; } // namespace V2_0 diff --git a/vehicle/2.0/types.hal b/vehicle/2.0/types.hal index 0e0e3ea707..72fa554273 100644 --- a/vehicle/2.0/types.hal +++ b/vehicle/2.0/types.hal @@ -2136,6 +2136,8 @@ enum VehiclePropertyChangeMode : int32_t { * the expected output. */ enum VehiclePropertyAccess : int32_t { + NONE = 0x00, + READ = 0x01, WRITE = 0x02, READ_WRITE = 0x03,