Merge "Implementing per-property access control"

This commit is contained in:
TreeHugger Robot
2016-12-08 01:26:15 +00:00
committed by Android (Google) Code Review
7 changed files with 561 additions and 24 deletions

View File

@@ -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 \

View File

@@ -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 <gtest/gtest.h>
#include <memory>
#include <fstream>
#include <unordered_set>
#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<VehicleProperty> supportedProperties {
VehicleProperty::HVAC_FAN_SPEED,
VehicleProperty::HVAC_FAN_DIRECTION,
};
parser.reset(new AccessControlConfigParser(supportedProperties));
}
public:
PropertyAclMap aclMap;
std::unique_ptr<AccessControlConfigParser> 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<unsigned> 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

View File

@@ -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 <android/log.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "AccessControlConfigParser.h"
namespace android {
namespace hardware {
namespace vehicle {
namespace V2_0 {
AccessControlConfigParser::AccessControlConfigParser(
const std::vector<VehicleProperty>& 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<int>(vehicleProperty);
numProp &= ~static_cast<int>(VehiclePropertyType::MASK)
& ~static_cast<int>(VehicleArea::MASK);
mStrippedToVehiclePropertyMap.emplace(numProp, vehicleProperty);
}
}
bool AccessControlConfigParser::parseFromStream(
std::istream* stream, PropertyAclMap* propertyAclMap) {
std::list<std::string> 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<std::string>* 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<int>(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<int>(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<unsigned>(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<int32_t>(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<VehiclePropertyAccess>(access);
return true;
}
void AccessControlConfigParser::split(const std::string& line,
std::list<std::string>* 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<std::string>* 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

View File

@@ -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 <string>
#include <vector>
#include <unordered_map>
#include <list>
#include <android/hardware/vehicle/2.0/types.h>
namespace android {
namespace hardware {
namespace vehicle {
namespace V2_0 {
struct PropertyAcl {
VehicleProperty propId;
unsigned uid;
VehiclePropertyAccess access;
};
using PropertyAclMap = std::unordered_multimap<VehicleProperty, PropertyAcl>;
/**
* 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<VehicleProperty>& properties);
/**
* Parses config content from given stream and writes results to
* propertyAclMap.
*/
bool parseFromStream(std::istream* stream, PropertyAclMap* propertyAclMap);
private:
bool processTokens(std::list<std::string>* 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<std::string>* tokens) const;
static bool parseInt(const char* strValue, int* outIntValue);
static void split(const std::string& line,
std::list<std::string>* outTokens);
private:
std::unordered_map<std::string, unsigned> mUidMap {}; // Contains UID
// aliases.
// Map property ids w/o TYPE and AREA to VehicleProperty.
std::unordered_map<int, VehicleProperty> mStrippedToVehiclePropertyMap;
};
} // namespace V2_0
} // namespace vehicle
} // namespace hardware
} // namespace android
#endif // android_hardware_vehicle_V2_0_AccessControlConfigParser_H_

View File

@@ -22,6 +22,8 @@
#include <hidl/Status.h>
#include <future>
#include <bitset>
#include <fstream>
#include <private/android_filesystem_config.h>
#include "VehicleHalManager.h"
@@ -52,10 +54,8 @@ Return<void> VehicleHalManager::getAllPropConfigs(
const_cast<VehiclePropConfig *>(halConfig.data()),
halConfig.size());
ALOGI("getAllPropConfigs calling callback");
_hidl_cb(hidlConfigs);
ALOGI("getAllPropConfigs done");
return Void();
}
@@ -88,7 +88,7 @@ Return<void> 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<StatusCode> 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<VehicleProperty> 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

View File

@@ -29,10 +29,11 @@
#include <android/hardware/vehicle/2.0/IVehicle.h>
#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<VehiclePropValuePtr> mEventQueue;
BatchingConsumer<VehiclePropValuePtr> mBatchingConsumer;
VehiclePropValuePool mValueObjectPool;
PropertyAclMap mPropertyAclMap;
};
} // namespace V2_0

View File

@@ -2136,6 +2136,8 @@ enum VehiclePropertyChangeMode : int32_t {
* the expected output.
*/
enum VehiclePropertyAccess : int32_t {
NONE = 0x00,
READ = 0x01,
WRITE = 0x02,
READ_WRITE = 0x03,