Introduce property types
Properties right now can take any format, but that makes it hard to specify an API for these properties as Treble intends to do. Therefore this change introduces the idea of property types, described below. 1) 'string' this is the default type and allows any property to be set. 2) 'bool' this allows only boolean values (true|false|1|0) 3) 'int' and 'uint' these allow signed and unsigned integer values respectively. 4) 'double' this allows floating point numbers with double precision. 5) 'size' this allows for strings matching [0-9]+[gkm]. 6) 'enum' this allows only a specific set of space deliminated values to be set, e.g. 'enum allow these strings' only allows one of 'allow', 'these', or 'strings' to be set. Bug: 70858511 Test: unit tests, test that properties are only set if their type matches Change-Id: I7a6b00fb43ec630d1f56c9e9a1f1b61d3914f603
This commit is contained in:
parent
4ba3ebbc2f
commit
927c5d5fdc
6 changed files with 327 additions and 62 deletions
|
@ -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",
|
||||
|
|
|
@ -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
81
init/property_type.cpp
Normal 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
30
init/property_type.h
Normal 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
|
95
init/property_type_test.cpp
Normal file
95
init/property_type_test.cpp
Normal 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
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue