blob: 32131dd1bc01b774d93fdda094b9b6fd0177d97d [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 "faced/face_service.h"
#include <signal.h>
#include <sys/socket.h>
#include <unistd.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/strings/string_piece.h>
#include "base/check.h"
#include "faced/util/status.h"
#include "faced/util/task.h"
namespace faced {
namespace {
// This socket number represents the socket that the FaceService app will
// communicate to the parent (faced) with via gRPC.
//
// This socket number needs to be consistent with the socket number that is set
// in the FaceService code of the binary that runs within the minijail
// environment.
inline constexpr int kChildSocket = 3;
// Path of the FaceService application
constexpr base::StringPiece kFaceServiceApplicationPath =
"/opt/google/faceauth/face_service";
} // namespace
absl::StatusOr<std::pair<base::ScopedFD, base::ScopedFD>> SocketPair() {
int fds[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, /*protocol=*/0, fds);
if (result < 0) {
return absl::InternalError("Could not create socket pair");
}
return {std::make_pair(base::ScopedFD(fds[0]), base::ScopedFD(fds[1]))};
}
// FaceServiceProcess method definitions below
absl::StatusOr<std::unique_ptr<FaceServiceProcess>>
FaceServiceProcess::Create() {
std::unique_ptr<FaceServiceProcess> process =
std::make_unique<FaceServiceProcess>();
absl::Status started_status = process->Start();
if (!started_status.ok()) {
return started_status;
}
return process;
}
absl::Status FaceServiceProcess::Start() {
// Start a minijail process containing the FaceService application
jail_ = ScopedMinijail(minijail_new());
// Prevent Linux capabilities our process has from being inherited by our
// child.
minijail_use_caps(jail_.get(), /*capmask=*/0);
minijail_namespace_vfs(jail_.get());
minijail_remount_proc_readonly(jail_.get());
minijail_namespace_pids(jail_.get());
minijail_namespace_net(jail_.get());
// Run the child job as user "nobody"
minijail_change_user(jail_.get(), "nobody");
// Change the group to "nobody"
minijail_change_group(jail_.get(), "nobody");
// Child process should inherit all supplementary groups of "nobody"
minijail_inherit_usergroups(jail_.get());
// Create a socket for communication with the child process
std::pair<base::ScopedFD, base::ScopedFD> sockets;
FACE_ASSIGN_OR_RETURN(sockets, SocketPair());
// Give the child process the other side of our socket pair. By
// convention, this is passed in as FD 1 (stdout).
minijail_preserve_fd(jail_.get(),
/*parent_fd=*/sockets.second.get(), kChildSocket);
if (VLOG_IS_ON(1)) {
// Preserve the child process's stdout & stederr FDs.
minijail_preserve_fd(jail_.get(), STDOUT_FILENO, STDOUT_FILENO);
minijail_preserve_fd(jail_.get(), STDERR_FILENO, STDERR_FILENO);
// Write debug from inside the minijail to STDERR.
minijail_log_to_fd(STDERR_FILENO, 7); // 7 is "debug" level
}
// Close all FDs in the child, other than those we explicitly configure above.
minijail_close_open_fds(jail_.get());
// Fork and exec FaceService from the child process
pid_t pid = -1;
char* const argv[] = {nullptr};
int ret = minijail_run_pid_pipes_no_preload(
jail_.get(), kFaceServiceApplicationPath.data(), argv, &pid, nullptr,
nullptr, nullptr);
if (ret != 0) {
return absl::InternalError("FaceService failed to start.");
}
VLOG(1) << "FaceService started (" << pid << ")";
// Close our FD to the child's socket.
sockets.second.reset();
fd_ = std::move(sockets.first);
return absl::OkStatus();
}
absl::StatusOr<std::unique_ptr<FaceServiceClient>>
FaceServiceProcess::CreateClient() {
if (!fd_.is_valid()) {
return absl::AlreadyExistsError("Client has already been created.");
}
return std::make_unique<FaceServiceClient>(std::move(fd_));
}
absl::Status FaceServiceProcess::ShutDown() {
fd_.reset();
// Kill the process running in minijail.
int ret = minijail_kill(jail_.get());
// Process exited with SIGTERM as expected.
if (ret == MINIJAIL_ERR_SIG_BASE + SIGTERM) {
return absl::OkStatus();
}
if (ret < 0) {
ret = -ret;
if (ret == ESRCH) {
// Process has already been waited for and we consider shutdown a success.
return absl::OkStatus();
}
}
return absl::UnknownError("Error stopping FaceService");
}
// FaceServiceClient method definitions below
FaceServiceClient::FaceServiceClient(base::ScopedFD fd) {
// Create the gRPC channel
channel_ = grpc::CreateInsecureChannelFromFd("", fd.release());
// Create a gRPC client that will be leased through FaceServiceClient
rpc_client_ =
std::make_unique<brillo::AsyncGrpcClient<faceauth::eora::FaceService>>(
CurrentSequence(), faceauth::eora::FaceService::NewStub(channel_));
}
brillo::AsyncGrpcClient<faceauth::eora::FaceService>*
FaceServiceClient::GetAsyncClient() {
return rpc_client_.get();
}
void FaceServiceClient::ShutDown() {
// Pass ownership of `rpc_client_` to the ShutDown callback.
//
// We can't delete the client until its async shut down operation
// has completed. To avoid forcing our own clients to wait, we pass
// ownership of the client to the closure, which will delete the client
// when shut down is complete.
brillo::AsyncGrpcClient<faceauth::eora::FaceService>* client =
rpc_client_.get();
client->ShutDown(base::BindOnce(
[](std::unique_ptr<brillo::AsyncGrpcClient<faceauth::eora::FaceService>>
client) {
// Client has been shut down, so it is now safe to clean up.
client.reset();
},
std::move(rpc_client_)));
}
FaceServiceClient::~FaceServiceClient() {
if (rpc_client_) {
ShutDown();
}
}
// FaceServiceManager method definitions below
std::unique_ptr<FaceServiceManager> FaceServiceManager::Create() {
std::unique_ptr<FaceServiceManager> mgr =
std::make_unique<FaceServiceManager>();
absl::StatusOr<std::unique_ptr<FaceServiceProcess>> process =
FaceServiceProcess::Create();
if (process.ok()) {
mgr->process_ = std::move(process.value());
}
return mgr;
}
absl::StatusOr<Lease<brillo::AsyncGrpcClient<faceauth::eora::FaceService>>>
FaceServiceManager::LeaseClient() {
if (leased_) {
return absl::AlreadyExistsError("Client already in use");
}
// Lazily start the process.
if (!process_) {
absl::StatusOr<std::unique_ptr<FaceServiceProcess>> process =
FaceServiceProcess::Create();
if (!process.ok()) {
return process.status();
}
process_ = std::move(process.value());
}
// Lazily create a client.
if (!client_) {
absl::StatusOr<std::unique_ptr<FaceServiceClient>> client =
process_->CreateClient();
if (!client.ok()) {
return client.status();
}
client_ = std::move(client.value());
}
leased_ = true;
return Lease<brillo::AsyncGrpcClient<faceauth::eora::FaceService>>(
client_->GetAsyncClient(),
base::BindOnce(&FaceServiceManager::OnReleaseClient,
base::Unretained(this)));
}
void FaceServiceManager::OnReleaseClient() {
leased_ = false;
}
FaceServiceManager::~FaceServiceManager() {
CHECK(leased_ == false)
<< "FaceServiceManager instance destroyed while client remains leased.";
if (process_) {
(void)process_->ShutDown();
process_.reset();
}
}
} // namespace faced