Merge "Introduce property types"

This commit is contained in:
Tom Cherry 2018-01-17 21:01:33 +00:00 committed by Gerrit Code Review
commit a0ffad60ef
6 changed files with 327 additions and 62 deletions

View File

@ -75,6 +75,7 @@ cc_library_static {
"persistent_properties.cpp",
"persistent_properties.proto",
"property_service.cpp",
"property_type.cpp",
"security.cpp",
"selinux.cpp",
"service.cpp",
@ -178,6 +179,7 @@ cc_test {
"init_test.cpp",
"persistent_properties_test.cpp",
"property_service_test.cpp",
"property_type_test.cpp",
"result_test.cpp",
"rlimit_parser_test.cpp",
"service_test.cpp",

View File

@ -58,6 +58,7 @@
#include "init.h"
#include "persistent_properties.h"
#include "property_type.h"
#include "util.h"
using android::base::ReadFileToString;
@ -95,14 +96,9 @@ void property_init() {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
static bool check_mac_perms(const std::string& name, char* sctx, struct ucred* cr) {
if (!sctx) {
return false;
}
const char* target_context = nullptr;
property_info_area->GetPropertyInfo(name.c_str(), &target_context, nullptr);
if (target_context == nullptr) {
static bool CheckMacPerms(const std::string& name, const char* target_context,
const char* source_context, struct ucred* cr) {
if (!target_context || !source_context) {
return false;
}
@ -111,29 +107,12 @@ static bool check_mac_perms(const std::string& name, char* sctx, struct ucred* c
audit_data.name = name.c_str();
audit_data.cr = cr;
bool has_access =
(selinux_check_access(sctx, target_context, "property_service", "set", &audit_data) == 0);
bool has_access = (selinux_check_access(source_context, target_context, "property_service",
"set", &audit_data) == 0);
return has_access;
}
static int check_control_mac_perms(const char *name, char *sctx, struct ucred *cr)
{
/*
* Create a name prefix out of ctl.<service name>
* The new prefix allows the use of the existing
* property service backend labeling while avoiding
* mislabels based on true property prefixes.
*/
char ctl_name[PROP_VALUE_MAX+4];
int ret = snprintf(ctl_name, sizeof(ctl_name), "ctl.%s", name);
if (ret < 0 || (size_t) ret >= sizeof(ctl_name))
return 0;
return check_mac_perms(ctl_name, sctx, cr);
}
bool is_legal_property_name(const std::string& name) {
size_t namelen = name.size();
@ -422,52 +401,70 @@ static void handle_property_set(SocketConnection& socket,
struct ucred cr = socket.cred();
char* source_ctx = nullptr;
getpeercon(socket.socket(), &source_ctx);
std::string source_context = source_ctx;
freecon(source_ctx);
if (StartsWith(name, "ctl.")) {
if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
// ctl. properties have their name ctl.<action> and their value is the name of the service to
// apply that action to. Permissions for these actions are based on the service, so we must
// create a fake name of ctl.<service> to check permissions.
auto control_string = "ctl." + value;
const char* target_context = nullptr;
const char* type = nullptr;
property_info_area->GetPropertyInfo(control_string.c_str(), &target_context, &type);
if (!CheckMacPerms(control_string, target_context, source_context.c_str(), &cr)) {
LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
<< " service ctl [" << value << "]"
<< " uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
}
return;
}
handle_control_message(name.c_str() + 4, value.c_str());
if (!legacy_protocol) {
socket.SendUint32(PROP_SUCCESS);
socket.SendUint32(PROP_SUCCESS);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
<< " service ctl [" << value << "]"
<< " uid:" << cr.uid
<< " gid:" << cr.gid
<< " pid:" << cr.pid;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
}
}
} else {
if (check_mac_perms(name, source_ctx, &cr)) {
const char* target_context = nullptr;
const char* type = nullptr;
property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
if (!CheckMacPerms(name, target_context, source_context.c_str(), &cr)) {
LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid
<< " name:" << name;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
}
return;
}
if (type == nullptr || !CheckType(type, value)) {
LOG(ERROR) << "sys_prop(" << cmd_name << "): type check failed, type: '"
<< (type ?: "(null)") << "' value: '" << value << "'";
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_INVALID_VALUE);
}
return;
}
// sys.powerctl is a special property that is used to make the device reboot. We want to log
// any process that sets this property to be able to accurately blame the cause of a shutdown.
if (name == "sys.powerctl") {
std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
std::string process_cmdline;
std::string process_log_string;
if (ReadFileToString(cmdline_path, &process_cmdline)) {
// Since cmdline is null deliminated, .c_str() conveniently gives us just the process path.
process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
std::string process_cmdline;
std::string process_log_string;
if (ReadFileToString(cmdline_path, &process_cmdline)) {
// Since cmdline is null deliminated, .c_str() conveniently gives us just the process path.
process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
}
uint32_t result = property_set(name, value);
if (!legacy_protocol) {
socket.SendUint32(result);
socket.SendUint32(result);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
}
}
}
freecon(source_ctx);
}
static void handle_property_set_fd() {
@ -764,9 +761,10 @@ void CreateSerializedPropertyInfo() {
}
LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
}
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "\\s*", &serialized_contexts,
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;

81
init/property_type.cpp Normal file
View File

@ -0,0 +1,81 @@
//
// Copyright (C) 2017 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 "property_type.h"
#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
using android::base::ParseDouble;
using android::base::ParseInt;
using android::base::ParseUint;
using android::base::Split;
namespace android {
namespace init {
bool CheckType(const std::string& type_string, const std::string& value) {
auto type_strings = Split(type_string, " ");
if (type_strings.empty()) {
return false;
}
auto type = type_strings[0];
if (type == "string") {
return true;
}
if (type == "bool") {
return value == "true" || value == "false" || value == "1" || value == "0";
}
if (type == "int") {
int64_t parsed;
return ParseInt(value, &parsed);
}
if (type == "uint") {
uint64_t parsed;
if (value.empty() || value.front() == '-') {
return false;
}
return ParseUint(value, &parsed);
}
if (type == "double") {
double parsed;
return ParseDouble(value.c_str(), &parsed);
}
if (type == "size") {
auto it = value.begin();
while (it != value.end() && isdigit(*it)) {
it++;
}
if (it == value.begin() || it == value.end() || (*it != 'g' && *it != 'k' && *it != 'm')) {
return false;
}
it++;
return it == value.end();
}
if (type == "enum") {
for (auto it = std::next(type_strings.begin()); it != type_strings.end(); ++it) {
if (*it == value) {
return true;
}
}
}
return false;
}
} // namespace init
} // namespace android

30
init/property_type.h Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2017 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 _INIT_PROPERTY_TYPE_H
#define _INIT_PROPERTY_TYPE_H
#include <string>
namespace android {
namespace init {
bool CheckType(const std::string& type_string, const std::string& value);
} // namespace init
} // namespace android
#endif

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 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 "property_type.h"
#include <gtest/gtest.h>
namespace android {
namespace init {
TEST(property_type, CheckType_string) {
EXPECT_TRUE(CheckType("string", ""));
EXPECT_TRUE(CheckType("string", "-234"));
EXPECT_TRUE(CheckType("string", "234"));
EXPECT_TRUE(CheckType("string", "true"));
EXPECT_TRUE(CheckType("string", "false"));
EXPECT_TRUE(CheckType("string", "45645634563456345634563456"));
EXPECT_TRUE(CheckType("string", "some other string"));
}
TEST(property_type, CheckType_int) {
EXPECT_FALSE(CheckType("int", ""));
EXPECT_FALSE(CheckType("int", "abc"));
EXPECT_FALSE(CheckType("int", "-abc"));
EXPECT_TRUE(CheckType("int", "0"));
EXPECT_TRUE(CheckType("int", std::to_string(std::numeric_limits<int64_t>::min())));
EXPECT_TRUE(CheckType("int", std::to_string(std::numeric_limits<int64_t>::max())));
EXPECT_TRUE(CheckType("int", "123"));
EXPECT_TRUE(CheckType("int", "-123"));
}
TEST(property_type, CheckType_uint) {
EXPECT_FALSE(CheckType("uint", ""));
EXPECT_FALSE(CheckType("uint", "abc"));
EXPECT_FALSE(CheckType("uint", "-abc"));
EXPECT_TRUE(CheckType("uint", "0"));
EXPECT_TRUE(CheckType("uint", std::to_string(std::numeric_limits<uint64_t>::max())));
EXPECT_TRUE(CheckType("uint", "123"));
EXPECT_FALSE(CheckType("uint", "-123"));
}
TEST(property_type, CheckType_double) {
EXPECT_FALSE(CheckType("double", ""));
EXPECT_FALSE(CheckType("double", "abc"));
EXPECT_FALSE(CheckType("double", "-abc"));
EXPECT_TRUE(CheckType("double", "0.0"));
EXPECT_TRUE(CheckType("double", std::to_string(std::numeric_limits<double>::min())));
EXPECT_TRUE(CheckType("double", std::to_string(std::numeric_limits<double>::max())));
EXPECT_TRUE(CheckType("double", "123.1"));
EXPECT_TRUE(CheckType("double", "-123.1"));
}
TEST(property_type, CheckType_size) {
EXPECT_FALSE(CheckType("size", ""));
EXPECT_FALSE(CheckType("size", "ab"));
EXPECT_FALSE(CheckType("size", "abcd"));
EXPECT_FALSE(CheckType("size", "0"));
EXPECT_TRUE(CheckType("size", "512g"));
EXPECT_TRUE(CheckType("size", "512k"));
EXPECT_TRUE(CheckType("size", "512m"));
EXPECT_FALSE(CheckType("size", "512gggg"));
EXPECT_FALSE(CheckType("size", "512mgk"));
EXPECT_FALSE(CheckType("size", "g"));
EXPECT_FALSE(CheckType("size", "m"));
}
TEST(property_type, CheckType_enum) {
EXPECT_FALSE(CheckType("enum abc", ""));
EXPECT_FALSE(CheckType("enum abc", "ab"));
EXPECT_FALSE(CheckType("enum abc", "abcd"));
EXPECT_FALSE(CheckType("enum 123 456 789", "0"));
EXPECT_TRUE(CheckType("enum abc", "abc"));
EXPECT_TRUE(CheckType("enum 123 456 789", "123"));
EXPECT_TRUE(CheckType("enum 123 456 789", "456"));
EXPECT_TRUE(CheckType("enum 123 456 789", "789"));
}
} // namespace init
} // namespace android

View File

@ -1,9 +1,26 @@
//
// Copyright (C) 2017 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 <property_info_serializer/property_info_serializer.h>
#include <android-base/strings.h>
#include "space_tokenizer.h"
using android::base::Join;
using android::base::Split;
using android::base::StartsWith;
using android::base::Trim;
@ -11,6 +28,34 @@ using android::base::Trim;
namespace android {
namespace properties {
namespace {
bool IsTypeValid(const std::vector<std::string>& type_strings) {
if (type_strings.empty()) {
return false;
}
// There must be at least one string following 'enum'
if (type_strings[0] == "enum") {
return type_strings.size() > 1;
}
// There should not be any string following any other types.
if (type_strings.size() != 1) {
return false;
}
// Check that the type matches one of remaining valid types.
static const char* const no_parameter_types[] = {"string", "bool", "int",
"uint", "double", "size"};
for (const auto& type : no_parameter_types) {
if (type_strings[0] == type) {
return true;
}
}
return false;
}
bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {
auto tokenizer = SpaceTokenizer(line);
@ -26,14 +71,28 @@ bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std:
return false;
}
// It is not an error to not find these, as older files will not contain them.
// It is not an error to not find exact_match or a type, as older files will not contain them.
auto exact_match = tokenizer.GetNext();
auto type = tokenizer.GetRemaining();
// We reformat type to be space deliminated regardless of the input whitespace for easier storage
// and subsequent parsing.
auto type_strings = std::vector<std::string>{};
auto type = tokenizer.GetNext();
while (!type.empty()) {
type_strings.emplace_back(type);
type = tokenizer.GetNext();
}
*out = {property, context, type, exact_match == "exact"};
if (!type_strings.empty() && !IsTypeValid(type_strings)) {
*error = "Type '" + Join(type_strings, " ") + "' is not valid";
return false;
}
*out = {property, context, Join(type_strings, " "), exact_match == "exact"};
return true;
}
} // namespace
void ParsePropertyInfoFile(const std::string& file_contents,
std::vector<PropertyInfoEntry>* property_infos,
std::vector<std::string>* errors) {