Merge "snapuserd: Allow connecting to the first-stage daemon."

This commit is contained in:
David Anderson 2021-07-28 21:59:59 +00:00 committed by Gerrit Code Review
commit 59c744bd3d
16 changed files with 300 additions and 40 deletions

View file

@ -206,6 +206,9 @@ bool OneShotInotify::ConsumeEvents() {
}
int64_t OneShotInotify::RemainingMs() const {
if (relative_timeout_ == std::chrono::milliseconds::max()) {
return std::chrono::milliseconds::max().count();
}
auto remaining = (std::chrono::steady_clock::now() - start_time_);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(remaining);
return (relative_timeout_ - elapsed).count();

View file

@ -23,6 +23,9 @@ namespace fs_mgr {
// Wait at most |relative_timeout| milliseconds for |path| to exist. dirname(path)
// must already exist. For example, to wait on /dev/block/dm-6, /dev/block must
// be a valid directory.
//
// If relative_timeout is std::chrono::milliseconds::max(), then the wait will
// block indefinitely.
bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout);
// Wait at most |relative_timeout| milliseconds for |path| to stop existing.

View file

@ -73,6 +73,7 @@ cc_defaults {
"libbrotli",
"libcutils_sockets",
"libdm",
"libfs_mgr",
"libgflags",
"liblog",
"libsnapshot_cow",

View file

@ -31,6 +31,7 @@ namespace snapshot {
static constexpr uint32_t PACKET_SIZE = 512;
static constexpr char kSnapuserdSocket[] = "snapuserd";
static constexpr char kSnapuserdSocketProxy[] = "snapuserd_proxy";
// Ensure that the second-stage daemon for snapuserd is running.
bool EnsureSnapuserdStarted();
@ -75,6 +76,9 @@ class SnapuserdClient {
// snapuserd to gracefully exit once all handler threads have terminated.
// This should only be used on first-stage instances of snapuserd.
bool DetachSnapuserd();
// Returns true if the snapuserd instance supports bridging a socket to second-stage init.
bool SupportsSecondStageSocketHandoff();
};
} // namespace snapshot

View file

@ -5,3 +5,12 @@ service snapuserd /system/bin/snapuserd
user root
group root system
seclabel u:r:snapuserd:s0
service snapuserd_proxy /system/bin/snapuserd -socket-handoff
socket snapuserd stream 0660 system system
socket snapuserd_proxy seqpacket 0660 system root
oneshot
disabled
user root
group root system
seclabel u:r:snapuserd:s0

View file

@ -141,6 +141,16 @@ bool SnapuserdClient::WaitForDeviceDelete(const std::string& control_device) {
return true;
}
bool SnapuserdClient::SupportsSecondStageSocketHandoff() {
std::string msg = "supports,second_stage_socket_handoff";
if (!Sendmsg(msg)) {
LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
return false;
}
std::string response = Receivemsg();
return response == "success";
}
std::string SnapuserdClient::Receivemsg() {
char msg[PACKET_SIZE];
ssize_t ret = TEMP_FAILURE_RETRY(recv(sockfd_, msg, sizeof(msg), 0));

View file

@ -26,6 +26,8 @@
DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
DEFINE_bool(no_socket, false,
"If true, no socket is used. Each additional argument is an INIT message.");
DEFINE_bool(socket_handoff, false,
"If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
namespace android {
namespace snapshot {
@ -33,8 +35,28 @@ namespace snapshot {
bool Daemon::StartServer(int argc, char** argv) {
int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
sigdelset(&signal_mask_, SIGUSR1);
// Masking signals here ensure that after this point, we won't handle INT/TERM
// until after we call into ppoll()
signal(SIGINT, Daemon::SignalHandler);
signal(SIGTERM, Daemon::SignalHandler);
signal(SIGPIPE, Daemon::SignalHandler);
signal(SIGUSR1, Daemon::SignalHandler);
MaskAllSignalsExceptIntAndTerm();
if (FLAGS_socket_handoff) {
return server_.RunForSocketHandoff();
}
if (!FLAGS_no_socket) {
return server_.Start(FLAGS_socket);
if (!server_.Start(FLAGS_socket)) {
return false;
}
return server_.Run();
}
for (int i = arg_start; i < argc; i++) {
@ -51,8 +73,7 @@ bool Daemon::StartServer(int argc, char** argv) {
// Skip the accept() call to avoid spurious log spam. The server will still
// run until all handlers have completed.
server_.SetTerminating();
return true;
return server_.WaitForSocket();
}
void Daemon::MaskAllSignalsExceptIntAndTerm() {
@ -61,6 +82,7 @@ void Daemon::MaskAllSignalsExceptIntAndTerm() {
sigdelset(&signal_mask, SIGINT);
sigdelset(&signal_mask, SIGTERM);
sigdelset(&signal_mask, SIGPIPE);
sigdelset(&signal_mask, SIGUSR1);
if (sigprocmask(SIG_SETMASK, &signal_mask, NULL) != 0) {
PLOG(ERROR) << "Failed to set sigprocmask";
}
@ -74,28 +96,14 @@ void Daemon::MaskAllSignals() {
}
}
void Daemon::Run() {
sigfillset(&signal_mask_);
sigdelset(&signal_mask_, SIGINT);
sigdelset(&signal_mask_, SIGTERM);
// Masking signals here ensure that after this point, we won't handle INT/TERM
// until after we call into ppoll()
signal(SIGINT, Daemon::SignalHandler);
signal(SIGTERM, Daemon::SignalHandler);
signal(SIGPIPE, Daemon::SignalHandler);
LOG(DEBUG) << "Snapuserd-server: ready to accept connections";
MaskAllSignalsExceptIntAndTerm();
server_.Run();
}
void Daemon::Interrupt() {
server_.Interrupt();
}
void Daemon::ReceivedSocketSignal() {
server_.ReceivedSocketSignal();
}
void Daemon::SignalHandler(int signal) {
LOG(DEBUG) << "Snapuserd received signal: " << signal;
switch (signal) {
@ -108,6 +116,11 @@ void Daemon::SignalHandler(int signal) {
LOG(ERROR) << "Received SIGPIPE signal";
break;
}
case SIGUSR1: {
LOG(INFO) << "Received SIGUSR1, attaching to proxy socket";
Daemon::Instance().ReceivedSocketSignal();
break;
}
default:
LOG(ERROR) << "Received unknown signal " << signal;
break;
@ -126,7 +139,5 @@ int main(int argc, char** argv) {
LOG(ERROR) << "Snapuserd daemon failed to start.";
exit(EXIT_FAILURE);
}
daemon.Run();
return 0;
}

View file

@ -36,8 +36,8 @@ class Daemon {
}
bool StartServer(int argc, char** argv);
void Run();
void Interrupt();
void ReceivedSocketSignal();
private:
// Signal mask used with ppoll()

View file

@ -25,14 +25,26 @@
#include <sys/types.h>
#include <unistd.h>
#include <android-base/cmsg.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <fs_mgr/file_wait.h>
#include <snapuserd/snapuserd_client.h>
#include "snapuserd.h"
#include "snapuserd_server.h"
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
namespace android {
namespace snapshot {
using namespace std::string_literals;
using android::base::borrowed_fd;
using android::base::unique_fd;
DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
if (input == "init") return DaemonOperations::INIT;
if (input == "start") return DaemonOperations::START;
@ -40,6 +52,7 @@ DaemonOperations SnapuserdServer::Resolveop(std::string& input) {
if (input == "query") return DaemonOperations::QUERY;
if (input == "delete") return DaemonOperations::DELETE;
if (input == "detach") return DaemonOperations::DETACH;
if (input == "supports") return DaemonOperations::SUPPORTS;
return DaemonOperations::INVALID;
}
@ -193,6 +206,16 @@ bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::strin
terminating_ = true;
return true;
}
case DaemonOperations::SUPPORTS: {
if (out.size() != 2) {
LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
return Sendmsg(fd, "fail");
}
if (out[1] == "second_stage_socket_handoff") {
return Sendmsg(fd, "success");
}
return Sendmsg(fd, "fail");
}
default: {
LOG(ERROR) << "Received unknown message type from client";
Sendmsg(fd, "fail");
@ -245,28 +268,36 @@ void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
}
bool SnapuserdServer::Start(const std::string& socketname) {
bool start_listening = true;
sockfd_.reset(android_get_control_socket(socketname.c_str()));
if (sockfd_ >= 0) {
if (listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed: " << socketname;
return false;
}
} else {
if (sockfd_ < 0) {
sockfd_.reset(socket_local_server(socketname.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM));
if (sockfd_ < 0) {
PLOG(ERROR) << "Failed to create server socket " << socketname;
return false;
}
start_listening = false;
}
return StartWithSocket(start_listening);
}
bool SnapuserdServer::StartWithSocket(bool start_listening) {
if (start_listening && listen(sockfd_.get(), 4) < 0) {
PLOG(ERROR) << "listen socket failed";
return false;
}
AddWatchedFd(sockfd_);
AddWatchedFd(sockfd_, POLLIN);
LOG(DEBUG) << "Snapuserd server successfully started with socket name " << socketname;
LOG(DEBUG) << "Snapuserd server now accepting connections";
return true;
}
bool SnapuserdServer::Run() {
LOG(INFO) << "Now listening on snapuserd socket";
while (!IsTerminating()) {
int rv = TEMP_FAILURE_RETRY(poll(watched_fds_.data(), watched_fds_.size(), -1));
if (rv < 0) {
@ -311,10 +342,10 @@ void SnapuserdServer::JoinAllThreads() {
}
}
void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd) {
void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
struct pollfd p = {};
p.fd = fd.get();
p.events = POLLIN;
p.events = events;
watched_fds_.emplace_back(std::move(p));
}
@ -325,7 +356,7 @@ void SnapuserdServer::AcceptClient() {
return;
}
AddWatchedFd(fd);
AddWatchedFd(fd, POLLIN);
}
bool SnapuserdServer::HandleClient(android::base::borrowed_fd fd, int revents) {
@ -422,5 +453,97 @@ bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
return true;
}
bool SnapuserdServer::WaitForSocket() {
auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
if (!android::fs_mgr::WaitForFile(socket_path, std::chrono::milliseconds::max())) {
LOG(ERROR)
<< "Failed to wait for proxy socket, second-stage snapuserd will fail to connect";
return false;
}
// We must re-initialize property service access, since we launched before
// second-stage init.
__system_properties_init();
if (!android::base::WaitForProperty("snapuserd.proxy_ready", "true")) {
LOG(ERROR)
<< "Failed to wait for proxy property, second-stage snapuserd will fail to connect";
return false;
}
unique_fd fd(socket_local_client(kSnapuserdSocketProxy, ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_SEQPACKET));
if (fd < 0) {
PLOG(ERROR) << "Failed to connect to socket proxy";
return false;
}
char code[1];
std::vector<unique_fd> fds;
ssize_t rv = android::base::ReceiveFileDescriptorVector(fd, code, sizeof(code), 1, &fds);
if (rv < 0) {
PLOG(ERROR) << "Failed to receive server socket over proxy";
return false;
}
if (fds.empty()) {
LOG(ERROR) << "Expected at least one file descriptor from proxy";
return false;
}
// We don't care if the ACK is received.
code[0] = 'a';
if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL) < 0)) {
PLOG(ERROR) << "Failed to send ACK to proxy";
return false;
}
sockfd_ = std::move(fds[0]);
if (!StartWithSocket(true)) {
return false;
}
return Run();
}
bool SnapuserdServer::RunForSocketHandoff() {
unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
if (proxy_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
}
borrowed_fd server_fd(android_get_control_socket(kSnapuserdSocket));
if (server_fd < 0) {
PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocket;
}
if (listen(proxy_fd.get(), 4) < 0) {
PLOG(FATAL) << "Proxy listen socket failed";
}
if (!android::base::SetProperty("snapuserd.proxy_ready", "true")) {
LOG(FATAL) << "Proxy failed to set ready property";
}
unique_fd client_fd(
TEMP_FAILURE_RETRY(accept4(proxy_fd.get(), nullptr, nullptr, SOCK_CLOEXEC)));
if (client_fd < 0) {
PLOG(FATAL) << "Proxy accept failed";
}
char code[1] = {'a'};
std::vector<int> fds = {server_fd.get()};
ssize_t rv = android::base::SendFileDescriptorVector(client_fd, code, sizeof(code), fds);
if (rv < 0) {
PLOG(FATAL) << "Proxy could not send file descriptor to snapuserd";
}
// Wait for an ACK - results don't matter, we just don't want to risk closing
// the proxy socket too early.
if (recv(client_fd, code, sizeof(code), 0) < 0) {
PLOG(FATAL) << "Proxy could not receive terminating code from snapuserd";
}
return true;
}
} // namespace snapshot
} // namespace android

View file

@ -42,6 +42,7 @@ enum class DaemonOperations {
STOP,
DELETE,
DETACH,
SUPPORTS,
INVALID,
};
@ -93,6 +94,7 @@ class SnapuserdServer : public Stoppable {
private:
android::base::unique_fd sockfd_;
bool terminating_;
volatile bool received_socket_signal_ = false;
std::vector<struct pollfd> watched_fds_;
std::mutex lock_;
@ -100,7 +102,7 @@ class SnapuserdServer : public Stoppable {
using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
HandlerList dm_users_;
void AddWatchedFd(android::base::borrowed_fd fd);
void AddWatchedFd(android::base::borrowed_fd fd, int events);
void AcceptClient();
bool HandleClient(android::base::borrowed_fd fd, int revents);
bool Recv(android::base::borrowed_fd fd, std::string* data);
@ -117,6 +119,7 @@ class SnapuserdServer : public Stoppable {
void RunThread(std::shared_ptr<DmUserHandler> handler);
void JoinAllThreads();
bool StartWithSocket(bool start_listening);
// Find a DmUserHandler within a lock.
HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
@ -129,6 +132,8 @@ class SnapuserdServer : public Stoppable {
bool Start(const std::string& socketname);
bool Run();
void Interrupt();
bool RunForSocketHandoff();
bool WaitForSocket();
std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
const std::string& cow_device_path,
@ -136,6 +141,7 @@ class SnapuserdServer : public Stoppable {
bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
void SetTerminating() { terminating_ = true; }
void ReceivedSocketSignal() { received_socket_signal_ = true; }
};
} // namespace snapshot

View file

@ -725,6 +725,40 @@ void SendLoadPersistentPropertiesMessage() {
}
}
static Result<void> ConnectEarlyStageSnapuserdAction(const BuiltinArguments& args) {
auto pid = GetSnapuserdFirstStagePid();
if (!pid) {
return {};
}
auto info = GetSnapuserdFirstStageInfo();
if (auto iter = std::find(info.begin(), info.end(), "socket"s); iter == info.end()) {
// snapuserd does not support socket handoff, so exit early.
return {};
}
// Socket handoff is supported.
auto svc = ServiceList::GetInstance().FindService("snapuserd");
if (!svc) {
LOG(FATAL) << "Failed to find snapuserd service entry";
}
svc->SetShutdownCritical();
svc->SetStartedInFirstStage(*pid);
svc = ServiceList::GetInstance().FindService("snapuserd_proxy");
if (!svc) {
LOG(FATAL) << "Failed find snapuserd_proxy service entry, merge will never initiate";
}
if (!svc->MarkSocketPersistent("snapuserd")) {
LOG(FATAL) << "Could not find snapuserd socket in snapuserd_proxy service entry";
}
if (auto result = svc->Start(); !result.ok()) {
LOG(FATAL) << "Could not start snapuserd_proxy: " << result.error();
}
return {};
}
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
@ -852,6 +886,7 @@ int SecondStageMain(int argc, char** argv) {
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...

View file

@ -269,6 +269,9 @@ void Service::Reap(const siginfo_t& siginfo) {
// Remove any socket resources we may have created.
for (const auto& socket : sockets_) {
if (socket.persist) {
continue;
}
auto path = ANDROID_SOCKET_DIR "/" + socket.name;
unlink(path.c_str());
}
@ -409,9 +412,7 @@ Result<void> Service::Start() {
}
bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
// Starting a service removes it from the disabled or reset state and
// immediately takes it out of the restarting state if it was in there.
flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
ResetFlagsForStart();
// Running processes require no additional work --- if they're in the
// process of exiting, we've ensured that they will immediately restart
@ -622,6 +623,23 @@ Result<void> Service::Start() {
return {};
}
void Service::SetStartedInFirstStage(pid_t pid) {
LOG(INFO) << "adding first-stage service '" << name_ << "'...";
time_started_ = boot_clock::now(); // not accurate, but doesn't matter here
pid_ = pid;
flags_ |= SVC_RUNNING;
start_order_ = next_start_order_++;
NotifyStateChange("running");
}
void Service::ResetFlagsForStart() {
// Starting a service removes it from the disabled or reset state and
// immediately takes it out of the restarting state if it was in there.
flags_ &= ~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START);
}
Result<void> Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
@ -792,5 +810,18 @@ Result<std::unique_ptr<Service>> Service::MakeTemporaryOneshotService(
nullptr, str_args, false);
}
// This is used for snapuserd_proxy, which hands off a socket to snapuserd. It's
// a special case to support the daemon launched in first-stage init. The persist
// feature is not part of the init language and is only used here.
bool Service::MarkSocketPersistent(const std::string& socket_name) {
for (auto& socket : sockets_) {
if (socket.name == socket_name) {
socket.persist = true;
return true;
}
}
return false;
}
} // namespace init
} // namespace android

View file

@ -99,6 +99,8 @@ class Service {
void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
reap_callbacks_.emplace_back(std::move(callback));
}
void SetStartedInFirstStage(pid_t pid);
bool MarkSocketPersistent(const std::string& socket_name);
size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
static bool is_exec_service_running() { return is_exec_service_running_; }
@ -144,6 +146,7 @@ class Service {
void StopOrReset(int how);
void KillProcessGroup(int signal, bool report_oneshot = false);
void SetProcessAttributesAndCaps();
void ResetFlagsForStart();
static unsigned long next_start_order_;
static bool is_exec_service_running_;

View file

@ -54,6 +54,7 @@ struct SocketDescriptor {
int perm = 0;
std::string context;
bool passcred = false;
bool persist = false;
// Create() creates the named unix domain socket in /dev/socket and returns a Descriptor object.
// It should be called when starting a service, before calling fork(), such that the socket is

View file

@ -54,6 +54,7 @@ using android::snapshot::SnapuserdClient;
static constexpr char kSnapuserdPath[] = "/system/bin/snapuserd";
static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
static constexpr char kSnapuserdFirstStageFdVar[] = "FIRST_STAGE_SNAPUSERD_FD";
static constexpr char kSnapuserdFirstStageInfoVar[] = "FIRST_STAGE_SNAPUSERD_INFO";
static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
@ -87,6 +88,14 @@ void LaunchFirstStageSnapuserd() {
_exit(127);
}
auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);
if (!client) {
LOG(FATAL) << "Could not connect to first-stage snapuserd";
}
if (client->SupportsSecondStageSocketHandoff()) {
setenv(kSnapuserdFirstStageInfoVar, "socket", 1);
}
setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1);
LOG(INFO) << "Relaunched snapuserd with pid: " << pid;
@ -328,5 +337,13 @@ bool IsFirstStageSnapuserdRunning() {
return GetSnapuserdFirstStagePid().has_value();
}
std::vector<std::string> GetSnapuserdFirstStageInfo() {
const char* pid_str = getenv(kSnapuserdFirstStageInfoVar);
if (!pid_str) {
return {};
}
return android::base::Split(pid_str, ",");
}
} // namespace init
} // namespace android

View file

@ -76,6 +76,9 @@ bool IsFirstStageSnapuserdRunning();
// Return the pid of the first-stage instances of snapuserd, if it was started.
std::optional<pid_t> GetSnapuserdFirstStagePid();
// Return snapuserd info strings that were set during first-stage init.
std::vector<std::string> GetSnapuserdFirstStageInfo();
// Save an open fd to /system/bin (in the ramdisk) into an environment. This is
// used to later execveat() snapuserd.
void SaveRamdiskPathToSnapuserd();