init: run property service in a thread

It's been a long standing issue that init cannot respond to property
set messages when it is running a builtin command.  This is
particularly problematic when the commands involve IPC to vold or
other daemons, as it prevents them from being able to set properties.

This change has init run property service in a thread, which
eliminates the above issue.

This change may also serve as a starting block to running property
service in an entirely different process to better isolate init from
handling property requests.

Test: CF boots, walleye boots, properties are set appropriately
Change-Id: Id9534a5916abb2f7d2a49cda54e33c1b69c50c2f
This commit is contained in:
Tom Cherry 2019-04-22 17:46:37 -07:00
parent 3707d328a1
commit 26f5e7da3a
10 changed files with 360 additions and 132 deletions

View file

@ -128,6 +128,7 @@ cc_library_static {
"persistent_properties.cpp",
"persistent_properties.proto",
"property_service.cpp",
"property_service.proto",
"property_type.cpp",
"reboot.cpp",
"reboot_utils.cpp",

View file

@ -80,6 +80,7 @@
using namespace std::literals::string_literals;
using android::base::Basename;
using android::base::StartsWith;
using android::base::unique_fd;
using android::fs_mgr::Fstab;
using android::fs_mgr::ReadFstabFromFile;
@ -687,6 +688,15 @@ static Result<void> do_swapon_all(const BuiltinArguments& args) {
}
static Result<void> do_setprop(const BuiltinArguments& args) {
if (StartsWith(args[1], "ctl.")) {
return Error()
<< "Cannot set ctl. properties from init; call the Service functions directly";
}
if (args[1] == kRestoreconProperty) {
return Error() << "Cannot set '" << kRestoreconProperty
<< "' from init; use the restorecon builtin directly";
}
property_set(args[1], args[2]);
return {};
}
@ -1002,7 +1012,20 @@ static Result<void> do_loglevel(const BuiltinArguments& args) {
}
static Result<void> do_load_persist_props(const BuiltinArguments& args) {
load_persist_props();
// Devices with FDE have load_persist_props called twice; the first time when the temporary
// /data partition is mounted and then again once /data is truly mounted. We do not want to
// read persistent properties from the temporary /data partition or mark persistent properties
// as having been loaded during the first call, so we return in that case.
std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
if (crypto_state == "encrypted" && crypto_type == "block") {
static size_t num_calls = 0;
if (++num_calls == 1) return {};
}
SendLoadPersistentPropertiesMessage();
start_waiting_for_property("ro.persistent_properties.ready", "true");
return {};
}

View file

@ -61,6 +61,7 @@
#include "mount_handler.h"
#include "mount_namespace.h"
#include "property_service.h"
#include "proto_utils.h"
#include "reboot.h"
#include "reboot_utils.h"
#include "security.h"
@ -69,6 +70,7 @@
#include "service.h"
#include "service_parser.h"
#include "sigchld_handler.h"
#include "system/core/init/property_service.pb.h"
#include "util.h"
using namespace std::chrono_literals;
@ -90,6 +92,7 @@ static int property_triggers_enabled = 0;
static char qemu[32];
static int signal_fd = -1;
static int property_fd = -1;
static std::unique_ptr<Timer> waiting_for_prop(nullptr);
static std::string wait_prop_name;
@ -613,6 +616,54 @@ static void RecordStageBoottimes(const boot_clock::time_point& second_stage_star
selinux_start_time_ns));
}
void SendLoadPersistentPropertiesMessage() {
auto init_message = InitMessage{};
init_message.set_load_persistent_properties(true);
if (auto result = SendMessage(property_fd, init_message); !result) {
LOG(ERROR) << "Failed to send load persistent properties message: " << result.error();
}
}
static void HandlePropertyFd() {
auto message = ReadMessage(property_fd);
if (!message) {
LOG(ERROR) << "Could not read message from property service: " << message.error();
return;
}
auto property_message = PropertyMessage{};
if (!property_message.ParseFromString(*message)) {
LOG(ERROR) << "Could not parse message from property service";
return;
}
switch (property_message.msg_case()) {
case PropertyMessage::kControlMessage: {
auto& control_message = property_message.control_message();
bool response = HandleControlMessage(control_message.msg(), control_message.name(),
control_message.pid());
auto init_message = InitMessage{};
auto* control_response = init_message.mutable_control_response();
control_response->set_result(response);
if (auto result = SendMessage(property_fd, init_message); !result) {
LOG(ERROR) << "Failed to send control response: " << result.error();
}
break;
}
case PropertyMessage::kChangedMessage: {
auto& changed_message = property_message.changed_message();
property_changed(changed_message.name(), changed_message.value());
break;
}
default:
LOG(ERROR) << "Unknown message type from property service: "
<< property_message.msg_case();
}
}
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
@ -684,7 +735,12 @@ int SecondStageMain(int argc, char** argv) {
UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
StartPropertyService(&epoll);
StartPropertyService(&property_fd);
if (auto result = epoll.RegisterHandler(property_fd, HandlePropertyFd); !result) {
LOG(FATAL) << "Could not register epoll handler for property fd: " << result.error();
}
MountHandler mount_handler(&epoll);
set_usb_controller();

View file

@ -31,16 +31,14 @@ namespace init {
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
Parser CreateServiceOnlyParser(ServiceList& service_list);
bool HandleControlMessage(const std::string& msg, const std::string& arg, pid_t pid);
void property_changed(const std::string& name, const std::string& value);
bool start_waiting_for_property(const char *name, const char *value);
void DumpState();
void ResetWaitForProp();
void SendLoadPersistentPropertiesMessage();
int SecondStageMain(int argc, char** argv);
} // namespace init

View file

@ -42,6 +42,7 @@
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <thread>
#include <vector>
@ -63,8 +64,10 @@
#include "init.h"
#include "persistent_properties.h"
#include "property_type.h"
#include "proto_utils.h"
#include "selinux.h"
#include "subcontext.h"
#include "system/core/init/property_service.pb.h"
#include "util.h"
using namespace std::literals;
@ -76,6 +79,7 @@ using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::Timer;
using android::base::Trim;
using android::base::unique_fd;
using android::base::WriteStringToFile;
using android::properties::BuildTrie;
using android::properties::ParsePropertyInfoFile;
@ -85,14 +89,15 @@ using android::properties::PropertyInfoEntry;
namespace android {
namespace init {
static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
static bool persistent_properties_loaded = false;
static int property_set_fd = -1;
static int init_socket = -1;
static PropertyInfoAreaFile property_info_area;
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr, std::string* error);
uint32_t InitPropertySet(const std::string& name, const std::string& value);
uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
@ -164,6 +169,17 @@ static bool CheckMacPerms(const std::string& name, const char* target_context,
return has_access;
}
static void SendPropertyChanged(const std::string& name, const std::string& value) {
auto property_msg = PropertyMessage{};
auto* changed_message = property_msg.mutable_changed_message();
changed_message->set_name(name);
changed_message->set_value(value);
if (auto result = SendMessage(init_socket, property_msg); !result) {
LOG(ERROR) << "Failed to send property changed message: " << result.error();
}
}
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
size_t valuelen = value.size();
@ -199,7 +215,11 @@ static uint32_t PropertySet(const std::string& name, const std::string& value, s
if (persistent_properties_loaded && StartsWith(name, "persist.")) {
WritePersistentProperty(name, value);
}
property_changed(name, value);
// If init hasn't started its main loop, then it won't be handling property changed messages
// anyway, so there's no need to try to send them.
if (init_socket != -1) {
SendPropertyChanged(name, value);
}
return PROP_SUCCESS;
}
@ -240,17 +260,6 @@ class AsyncRestorecon {
};
uint32_t InitPropertySet(const std::string& name, const std::string& value) {
if (StartsWith(name, "ctl.")) {
LOG(ERROR) << "InitPropertySet: Do not set ctl. properties from init; call the Service "
"functions directly";
return PROP_ERROR_INVALID_NAME;
}
if (name == kRestoreconProperty) {
LOG(ERROR) << "InitPropertySet: Do not set '" << kRestoreconProperty
<< "' from init; use the restorecon builtin directly";
return PROP_ERROR_INVALID_NAME;
}
uint32_t result = 0;
ucred cr = {.pid = 1, .uid = 0, .gid = 0};
std::string error;
@ -266,8 +275,6 @@ class SocketConnection {
public:
SocketConnection(int socket, const ucred& cred) : socket_(socket), cred_(cred) {}
~SocketConnection() { close(socket_); }
bool RecvUint32(uint32_t* value, uint32_t* timeout_ms) {
return RecvFully(value, sizeof(*value), timeout_ms);
}
@ -389,12 +396,62 @@ class SocketConnection {
return bytes_left == 0;
}
int socket_;
unique_fd socket_;
ucred cred_;
DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection);
};
// Init responds with whether or not the control message was successful. However, init may set
// properties in the process of handling the control message, particularly when starting services.
// Therefore we cannot block in SendControlMessage() to wait for init's response. Instead, we store
// the SocketConnection for the socket that sent the control message. We then return to the main
// poll loop and handle messages until we get the response from init.
//
// Note that this is a queue, since it is possible for more control messages to come while init is
// handling the first. Both init and property service will handle these in order.
//
// Also note that the 1st version of the property service does not expect a result to be sent, so
// we have a nullopt as a placeholder in the queue to keep track of which control messages have been
// responded to.
static std::queue<std::optional<SocketConnection>> pending_control_message_results;
static uint32_t SendControlMessage(const std::string& msg, const std::string& name, pid_t pid,
std::string* error) {
auto property_msg = PropertyMessage{};
auto* control_message = property_msg.mutable_control_message();
control_message->set_msg(msg);
control_message->set_name(name);
control_message->set_pid(pid);
if (auto result = SendMessage(init_socket, property_msg); !result) {
*error = "Failed to send control message: " + result.error().message();
return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
}
return PROP_SUCCESS;
}
void HandleControlResponse(const InitMessage& init_message) {
if (pending_control_message_results.empty()) {
LOG(ERROR) << "Got a control response without pending control messages";
return;
}
if (!pending_control_message_results.front().has_value()) {
pending_control_message_results.pop();
return;
}
if (!pending_control_message_results.front().has_value()) {
return;
}
auto& control_response = init_message.control_response();
uint32_t response =
control_response.result() ? PROP_SUCCESS : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
pending_control_message_results.front()->SendUint32(response);
pending_control_message_results.pop();
}
bool CheckControlPropertyPerms(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr) {
// We check the legacy method first but these properties are dontaudit, so we only log an audit
@ -468,9 +525,7 @@ uint32_t HandlePropertySet(const std::string& name, const std::string& value,
}
if (StartsWith(name, "ctl.")) {
return HandleControlMessage(name.c_str() + 4, value, cr.pid)
? PROP_SUCCESS
: PROP_ERROR_HANDLE_CONTROL_MESSAGE;
return SendControlMessage(name.c_str() + 4, value, cr.pid, error);
}
// sys.powerctl is a special property that is used to make the device reboot. We want to log
@ -555,6 +610,10 @@ static void handle_property_set_fd() {
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
if (result == PROP_SUCCESS && StartsWith(prop_name, "ctl.")) {
pending_control_message_results.emplace(std::nullopt);
}
break;
}
@ -582,7 +641,12 @@ static void handle_property_set_fd() {
LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid
<< " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
socket.SendUint32(result);
if (result == PROP_SUCCESS && StartsWith(name, "ctl.")) {
pending_control_message_results.emplace(std::move(socket));
} else {
socket.SendUint32(result);
}
break;
}
@ -741,33 +805,6 @@ static void load_override_properties() {
}
}
/* When booting an encrypted system, /data is not mounted when the
* property service is started, so any properties stored there are
* not loaded. Vold triggers init to load these properties once it
* has mounted /data.
*/
void load_persist_props(void) {
// Devices with FDE have load_persist_props called twice; the first time when the temporary
// /data partition is mounted and then again once /data is truly mounted. We do not want to
// read persistent properties from the temporary /data partition or mark persistent properties
// as having been loaded during the first call, so we return in that case.
std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
if (crypto_state == "encrypted" && crypto_type == "block") {
static size_t num_calls = 0;
if (++num_calls == 1) return;
}
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
auto persistent_properties = LoadPersistentProperties();
for (const auto& persistent_property_record : persistent_properties.properties()) {
property_set(persistent_property_record.name(), persistent_property_record.value());
}
persistent_properties_loaded = true;
property_set("ro.persistent_properties.ready", "true");
}
// If the ro.product.[brand|device|manufacturer|model|name] properties have not been explicitly
// set, derive them from ro.product.${partition}.* properties
static void property_initialize_ro_product_props() {
@ -985,21 +1022,87 @@ void CreateSerializedPropertyInfo() {
selinux_android_restorecon(kPropertyInfosPath, 0);
}
void StartPropertyService(Epoll* epoll) {
static void HandleInitSocket() {
auto message = ReadMessage(init_socket);
if (!message) {
LOG(ERROR) << "Could not read message from init_dedicated_recv_socket: " << message.error();
return;
}
auto init_message = InitMessage{};
if (!init_message.ParseFromString(*message)) {
LOG(ERROR) << "Could not parse message from init";
return;
}
switch (init_message.msg_case()) {
case InitMessage::kControlResponse: {
HandleControlResponse(init_message);
break;
}
case InitMessage::kLoadPersistentProperties: {
load_override_properties();
// Read persistent properties after all default values have been loaded.
auto persistent_properties = LoadPersistentProperties();
for (const auto& persistent_property_record : persistent_properties.properties()) {
InitPropertySet(persistent_property_record.name(),
persistent_property_record.value());
}
InitPropertySet("ro.persistent_properties.ready", "true");
persistent_properties_loaded = true;
break;
}
default:
LOG(ERROR) << "Unknown message type from init: " << init_message.msg_case();
}
}
static void PropertyServiceThread() {
Epoll epoll;
if (auto result = epoll.Open(); !result) {
LOG(FATAL) << result.error();
}
if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
LOG(FATAL) << result.error();
}
if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result) {
LOG(FATAL) << result.error();
}
while (true) {
if (auto result = epoll.Wait(std::nullopt); !result) {
LOG(ERROR) << result.error();
}
}
}
void StartPropertyService(int* epoll_socket) {
property_set("ro.property_service.version", "2");
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, {})) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
PLOG(FATAL) << "Failed to socketpair() between property_service and init";
}
*epoll_socket = sockets[0];
init_socket = sockets[1];
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC, false, 0666, 0, 0,
{})) {
property_set_fd = *result;
} else {
PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
listen(property_set_fd, 8);
if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
std::thread{PropertyServiceThread}.detach();
property_set = [](const std::string& key, const std::string& value) -> uint32_t {
android::base::SetProperty(key, value);
return 0;
};
}
} // namespace init

View file

@ -25,17 +25,15 @@
namespace android {
namespace init {
static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
bool CanReadProperty(const std::string& source_context, const std::string& name);
extern uint32_t (*property_set)(const std::string& name, const std::string& value);
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr, std::string* error);
void property_init();
void property_load_boot_defaults(bool load_debug_prop);
void load_persist_props();
void StartPropertyService(Epoll* epoll);
void StartPropertyService(int* epoll_socket);
} // namespace init
} // namespace android

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2019 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.
*/
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
message PropertyMessage {
message ControlMessage {
optional string msg = 1;
optional string name = 2;
optional int32 pid = 3;
}
message ChangedMessage {
optional string name = 1;
optional string value = 2;
}
oneof msg {
ControlMessage control_message = 1;
ChangedMessage changed_message = 2;
};
}
message InitMessage {
message ControlResponse { optional bool result = 1; }
oneof msg {
ControlResponse control_response = 1;
bool load_persistent_properties = 2;
};
}

62
init/proto_utils.h Normal file
View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2019 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.
*/
#pragma once
#include <sys/socket.h>
#include <unistd.h>
#include <string>
#include "result.h"
namespace android {
namespace init {
constexpr size_t kBufferSize = 4096;
inline Result<std::string> ReadMessage(int socket) {
char buffer[kBufferSize] = {};
auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
if (result == 0) {
return Error();
} else if (result < 0) {
return ErrnoError();
}
return std::string(buffer, result);
}
template <typename T>
Result<void> SendMessage(int socket, const T& message) {
std::string message_string;
if (!message.SerializeToString(&message_string)) {
return Error() << "Unable to serialize message";
}
if (message_string.size() > kBufferSize) {
return Error() << "Serialized message too long to send";
}
if (auto result =
TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
result != static_cast<long>(message_string.size())) {
return ErrnoError() << "send() failed to send message contents";
}
return {};
}
} // namespace init
} // namespace android

View file

@ -18,16 +18,17 @@
#include <fcntl.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <selinux/android.h>
#include "action.h"
#include "builtins.h"
#include "proto_utils.h"
#include "util.h"
#if defined(__ANDROID__)
@ -59,45 +60,6 @@ const char* const paths_and_secontexts[2][2] = {
namespace {
constexpr size_t kBufferSize = 4096;
Result<std::string> ReadMessage(int socket) {
char buffer[kBufferSize] = {};
auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
if (result == 0) {
return Error();
} else if (result < 0) {
return ErrnoError();
}
return std::string(buffer, result);
}
template <typename T>
Result<void> SendMessage(int socket, const T& message) {
std::string message_string;
if (!message.SerializeToString(&message_string)) {
return Error() << "Unable to serialize message";
}
if (message_string.size() > kBufferSize) {
return Error() << "Serialized message too long to send";
}
if (auto result =
TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
result != static_cast<long>(message_string.size())) {
return ErrnoError() << "send() failed to send message contents";
}
return {};
}
std::vector<std::pair<std::string, std::string>> properties_to_set;
uint32_t SubcontextPropertySet(const std::string& name, const std::string& value) {
properties_to_set.emplace_back(name, value);
return 0;
}
class SubcontextProcess {
public:
SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd)
@ -131,14 +93,6 @@ void SubcontextProcess::RunCommand(const SubcontextCommand::ExecuteCommand& exec
result = RunBuiltinFunction(map_result->function, args, context_);
}
for (const auto& [name, value] : properties_to_set) {
auto property = reply->add_properties_to_set();
property->set_name(name);
property->set_value(value);
}
properties_to_set.clear();
if (result) {
reply->set_success(true);
} else {
@ -224,7 +178,10 @@ int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map
SelabelInitialize();
property_set = SubcontextPropertySet;
property_set = [](const std::string& key, const std::string& value) -> uint32_t {
android::base::SetProperty(key, value);
return 0;
};
auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
subcontext_process.MainLoop();
@ -311,15 +268,6 @@ Result<void> Subcontext::Execute(const std::vector<std::string>& args) {
return subcontext_reply.error();
}
for (const auto& property : subcontext_reply->properties_to_set()) {
ucred cr = {.pid = pid_, .uid = 0, .gid = 0};
std::string error;
if (HandlePropertySet(property.name(), property.value(), context_, cr, &error) != 0) {
LOG(ERROR) << "Subcontext init could not set '" << property.name() << "' to '"
<< property.value() << "': " << error;
}
}
if (subcontext_reply->reply_case() == SubcontextReply::kFailure) {
auto& failure = subcontext_reply->failure();
return ResultError(failure.error_string(), failure.error_errno());

View file

@ -38,10 +38,4 @@ message SubcontextReply {
Failure failure = 2;
ExpandArgsReply expand_args_reply = 3;
}
message PropertyToSet {
optional string name = 1;
optional string value = 2;
}
repeated PropertyToSet properties_to_set = 4;
}