blob: 28cae80e35ab58c417a70be732ef3df3bbce2d06 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo_service_manager/daemon/daemon.h"
#include <linux/limits.h>
#include <pwd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sysexits.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <base/check_op.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/task/single_thread_task_runner.h>
#include <chromeos/constants/mojo_service_manager.h>
#include <mojo/public/cpp/platform/platform_channel_endpoint.h>
#include <mojo/public/cpp/system/invitation.h>
#include "mojo_service_manager/daemon/service_manager.h"
#include "mojo_service_manager/daemon/service_policy_loader.h"
namespace chromeos::mojo_service_manager {
namespace {
// Allow others to write so others can connect to the socket. The ACLs are
// controlled by the policy files so we don't need to do it again by the system
// user / group.
constexpr mode_t kSocketMode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
base::ScopedFD CreateUnixDomainSocket(const base::FilePath& socket_path) {
base::ScopedFD socket_fd{
socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)};
if (!socket_fd.is_valid()) {
PLOG(ERROR) << "Failed to create socket.";
return base::ScopedFD{};
}
struct sockaddr_un unix_addr {
.sun_family = AF_UNIX,
};
constexpr size_t kMaxSize =
sizeof(unix_addr.sun_path) - /*NULL-terminator*/ 1;
CHECK_LE(socket_path.value().size(), kMaxSize);
strncpy(unix_addr.sun_path, socket_path.value().c_str(), kMaxSize);
if (bind(socket_fd.get(), reinterpret_cast<const sockaddr*>(&unix_addr),
sizeof(unix_addr)) < 0) {
PLOG(ERROR) << "Failed to bind: " << socket_path.value();
return base::ScopedFD{};
}
if (!base::SetPosixFilePermissions(socket_path, kSocketMode)) {
PLOG(ERROR) << "Failed to chmod the socket: " << socket_path.value();
return base::ScopedFD{};
}
if (listen(socket_fd.get(), SOMAXCONN) < 0) {
PLOG(ERROR) << "Failed to listen " << socket_path.value();
return base::ScopedFD{};
}
return socket_fd;
}
base::ScopedFD AcceptSocket(const base::ScopedFD& server_fd) {
return base::ScopedFD{HANDLE_EINTR(accept4(server_fd.get(), nullptr, nullptr,
SOCK_NONBLOCK | SOCK_CLOEXEC))};
}
mojo::PendingReceiver<mojom::ServiceManager> SendMojoInvitationAndPassReceiver(
base::ScopedFD peer) {
mojo::OutgoingInvitation invitation;
mojo::PendingReceiver<mojom::ServiceManager> receiver{
invitation.AttachMessagePipe(kMojoInvitationPipeName)};
mojo::OutgoingInvitation::Send(
std::move(invitation), base::kNullProcessHandle,
mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(peer))));
return receiver;
}
} // namespace
std::string GetSEContextStringFromChar(const char* buf, size_t len) {
// The length may or may not contains the null-terminator.
if (len > 0 && buf[len - 1] == '\0') {
return std::string(buf);
}
return std::string(buf, len);
}
Daemon::Delegate::Delegate() = default;
Daemon::Delegate::~Delegate() = default;
int Daemon::Delegate::GetSockOpt(const base::ScopedFD& socket,
int level,
int optname,
void* optval,
socklen_t* optlen) const {
return getsockopt(socket.get(), level, optname, optval, optlen);
}
const struct passwd* Daemon::Delegate::GetPWUid(uid_t uid) const {
return getpwuid(uid);
}
ServicePolicyMap Daemon::Delegate::LoadPolicyFiles(
const std::vector<base::FilePath>& policy_dir_paths) const {
ServicePolicyMap res;
LoadAllServicePolicyFileFromDirectories(policy_dir_paths, &res);
return res;
}
Daemon::Daemon(Delegate* delegate,
const base::FilePath& socket_path,
const std::vector<base::FilePath>& policy_dir_paths,
Configuration configuration)
: ipc_support_(std::make_unique<mojo::core::ScopedIPCSupport>(
base::SingleThreadTaskRunner::GetCurrentDefault(),
// Don't block shutdown. All mojo pipes are not expected to work after
// the broker shutdown, so we don't need to wait them.
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST)),
delegate_(delegate),
socket_path_(socket_path),
policy_dir_paths_(std::move(policy_dir_paths)),
configuration_(std::move(configuration)) {}
Daemon::~Daemon() {}
int Daemon::OnInit() {
int ret = brillo::Daemon::OnInit();
if (ret != EX_OK)
return ret;
// Creates the socket as early as possible to reduce the time of clients that
// poll and wait for the socket file to be created.
socket_fd_ = CreateUnixDomainSocket(socket_path_);
if (!socket_fd_.is_valid()) {
LOG(ERROR) << "Failed to create socket server at path: " << socket_path_;
return EX_OSERR;
}
service_manager_ = std::make_unique<ServiceManager>(
std::move(configuration_), delegate_->LoadPolicyFiles(policy_dir_paths_));
socket_watcher_ = base::FileDescriptorWatcher::WatchReadable(
socket_fd_.get(),
base::BindRepeating(&Daemon::SendMojoInvitationAndBindReceiver,
base::Unretained(this)));
LOG(INFO) << "mojo_service_manager started.";
return EX_OK;
}
void Daemon::OnShutdown(int* exit_code) {
LOG(INFO) << "mojo_service_manager is shutdowning with exit code: "
<< *exit_code;
// Manually reset these objects to prevent them posting tasks to the message
// loop during shutdowning.
socket_watcher_.reset();
service_manager_.reset();
socket_fd_.reset();
// This need to be reset manually to trigger the shutdown of mojo. Otherwise,
// the mojo broker tasks could block the message queue during shutdowning.
ipc_support_.reset();
}
void Daemon::SendMojoInvitationAndBindReceiver() {
base::ScopedFD peer = AcceptSocket(socket_fd_);
if (!peer.is_valid()) {
LOG(ERROR) << "Failed to accept peer socket";
return;
}
mojom::ProcessIdentityPtr identity = GetProcessIdentityFromPeerSocket(peer);
mojo::PendingReceiver<mojom::ServiceManager> receiver =
SendMojoInvitationAndPassReceiver(std::move(peer));
if (!identity) {
receiver.ResetWithReason(
static_cast<uint32_t>(mojom::ErrorCode::kUnexpectedOsError),
"Cannot get identity from peer socket.");
return;
}
// TODO(b/234569073): Remove this log after we fully enable service manager.
LOG(INFO) << "Receive connection from: " << identity->security_context << ", "
<< identity->uid << "("
<< identity->username.value_or("unknown user") << ")";
CHECK(service_manager_);
service_manager_->AddReceiver(std::move(identity), std::move(receiver));
}
std::optional<std::string> Daemon::GetUsernameByUid(uint32_t uid) const {
const struct passwd* passwd = nullptr;
do {
static_assert(sizeof(uid_t) == sizeof(uint32_t));
passwd = delegate_->GetPWUid(static_cast<uid_t>(uid));
} while (passwd == nullptr && errno == EINTR);
if (passwd == nullptr) {
PLOG(ERROR) << "Failed to get username by uid " << uid;
return std::nullopt;
}
return std::string{passwd->pw_name};
}
mojom::ProcessIdentityPtr Daemon::GetProcessIdentityFromPeerSocket(
const base::ScopedFD& peer) const {
auto identity = mojom::ProcessIdentity::New();
struct ucred ucred_data {};
socklen_t len = sizeof(ucred_data);
if (delegate_->GetSockOpt(peer, SOL_SOCKET, SO_PEERCRED, &ucred_data, &len) <
0) {
PLOG(ERROR) << "Failed to get SO_PEERCRED from peer socket.";
return nullptr;
}
static_assert(sizeof(ucred_data.pid) == 4);
static_assert(sizeof(ucred_data.uid) == 4);
static_assert(sizeof(ucred_data.gid) == 4);
identity->pid = static_cast<uint32_t>(ucred_data.pid);
identity->uid = static_cast<uint32_t>(ucred_data.uid);
identity->gid = static_cast<uint32_t>(ucred_data.gid);
identity->username = GetUsernameByUid(identity->uid);
// TODO(b/333323875): Don't get `security_context` after migration.
char buf[NAME_MAX] = {};
len = NAME_MAX;
if (delegate_->GetSockOpt(peer, SOL_SOCKET, SO_PEERSEC, &buf, &len) < 0) {
PLOG(ERROR) << "Failed to get SO_PEERSEC from peer socket.";
return nullptr;
}
identity->security_context = GetSEContextStringFromChar(buf, len);
if (identity->security_context.size() == 0) {
LOG(ERROR) << "The length of security context gotten from socket is 0.";
return nullptr;
}
return identity;
}
} // namespace chromeos::mojo_service_manager