diff --git a/init/Android.bp b/init/Android.bp index 2fea359a6..0db78249f 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -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", diff --git a/init/property_service.cpp b/init/property_service.cpp index 7aa94b09b..79f7f25b7 100644 --- a/init/property_service.cpp +++ b/init/property_service.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. - * 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. 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. 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; diff --git a/init/property_type.cpp b/init/property_type.cpp new file mode 100644 index 000000000..249b12b99 --- /dev/null +++ b/init/property_type.cpp @@ -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 +#include +#include + +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 diff --git a/init/property_type.h b/init/property_type.h new file mode 100644 index 000000000..c889e16c7 --- /dev/null +++ b/init/property_type.h @@ -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 + +namespace android { +namespace init { + +bool CheckType(const std::string& type_string, const std::string& value); + +} // namespace init +} // namespace android + +#endif diff --git a/init/property_type_test.cpp b/init/property_type_test.cpp new file mode 100644 index 000000000..068bccc8d --- /dev/null +++ b/init/property_type_test.cpp @@ -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 + +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::min()))); + EXPECT_TRUE(CheckType("int", std::to_string(std::numeric_limits::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::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::min()))); + EXPECT_TRUE(CheckType("double", std::to_string(std::numeric_limits::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 diff --git a/property_service/libpropertyinfoserializer/property_info_file.cpp b/property_service/libpropertyinfoserializer/property_info_file.cpp index bf96d88bf..2cdc62dce 100644 --- a/property_service/libpropertyinfoserializer/property_info_file.cpp +++ b/property_service/libpropertyinfoserializer/property_info_file.cpp @@ -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 #include #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& 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{}; + 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* property_infos, std::vector* errors) {