blob: a33df5721b09443fda93c4cce68fbd10c7457817 [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_manager/subprocess.h"
#include <algorithm>
#include <memory>
#include <vector>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <base/logging.h>
#include <base/posix/file_descriptor_shuffle.h>
#include <base/process/launch.h>
#include <base/time/time.h>
#include "login_manager/session_manager_service.h"
#include "login_manager/system_utils.h"
namespace login_manager {
Subprocess::Subprocess(uid_t uid, SystemUtils* system)
: pid_(-1),
desired_uid_(uid),
new_mount_namespace_(false),
system_(system) {}
Subprocess::~Subprocess() {}
void Subprocess::UseNewMountNamespace() {
new_mount_namespace_ = true;
}
// The reason that this method looks complex is because it's doing a
// bunch of work to keep the code between fork() and exec/exit simple
// and mostly async signal safe. This is because using fork() in a
// multithreaded process can create a child with inconsistent state
// (e.g. locks held by other threads remain locked). While glibc
// generally handles this gracefully internally, other libs are not as
// reliable (including base).
bool Subprocess::ForkAndExec(const std::vector<std::string>& args,
const std::vector<std::string>& env_vars) {
gid_t gid = 0;
std::vector<gid_t> groups;
if (desired_uid_ != 0 &&
!system_->GetGidAndGroups(desired_uid_, &gid, &groups)) {
LOG(ERROR) << "Can't get group info for " << desired_uid_;
return false;
}
std::unique_ptr<char const* []> argv(new char const*[args.size() + 1]);
for (size_t i = 0; i < args.size(); ++i)
argv[i] = args[i].c_str();
argv[args.size()] = 0;
std::unique_ptr<char const* []> envp(new char const*[env_vars.size() + 1]);
for (size_t i = 0; i < env_vars.size(); ++i)
envp[i] = env_vars[i].c_str();
envp[env_vars.size()] = 0;
// The browser should not inherit FDs other than stdio, stdin and stdout,
// including the logging FD. base::CloseSuperfluousFds() can do this, but
// it takes a map of FDs to keep open, and creating this map requires
// allocating memory in a way which is not safe to do after forking, so do it
// up here in the parent.
base::InjectiveMultimap saved_fds;
saved_fds.push_back(base::InjectionArc(STDIN_FILENO, STDIN_FILENO, false));
saved_fds.push_back(base::InjectionArc(STDOUT_FILENO, STDOUT_FILENO, false));
saved_fds.push_back(base::InjectionArc(STDERR_FILENO, STDERR_FILENO, false));
// Block all signals before the fork so that we can avoid a race in which the
// child executes configured signal handlers before the default handlers are
// installed, below. In the parent, we restore original signal blocks
// immediately after fork.
sigset_t new_sigset, old_sigset;
sigfillset(&new_sigset);
CHECK_EQ(0, sigprocmask(SIG_SETMASK, &new_sigset, &old_sigset));
pid_t fork_ret = system_->fork();
if (fork_ret == 0) {
// Reset signal handlers to default and masks to none per 'man 7 daemon'.
struct sigaction action = {};
action.sa_handler = SIG_DFL;
for (int i = 1; i < _NSIG; i++) {
// Don't check rvalue because some signals can't have handlers (e.g.
// KILL).
sigaction(i, &action, nullptr);
}
CHECK_EQ(0, sigprocmask(SIG_UNBLOCK, &new_sigset, nullptr));
if (new_mount_namespace_)
CHECK(system_->EnterNewMountNamespace());
// We try to set our UID/GID to the desired UID, and then exec
// the command passed in.
if (desired_uid_ != 0) {
int exit_code = system_->SetIDs(desired_uid_, gid, groups);
if (exit_code)
_exit(exit_code);
}
system_->CloseSuperfluousFds(saved_fds);
if (system_->execve(base::FilePath(argv[0]),
const_cast<char* const*>(argv.get()),
const_cast<char* const*>(envp.get()))) {
// Should never get here, unless we couldn't exec the command.
RAW_LOG(ERROR, "Error executing...");
RAW_LOG(ERROR, argv[0]);
_exit(errno == E2BIG ? ChildJobInterface::kCantSetEnv
: ChildJobInterface::kCantExec);
}
return false; // To make the compiler happy.
}
CHECK_EQ(0, sigprocmask(SIG_SETMASK, &old_sigset, nullptr));
if (fork_ret > 0) {
pid_ = fork_ret;
}
return pid_.has_value();
}
void Subprocess::KillEverything(int signal) {
DCHECK(pid_.has_value());
if (system_->kill(-pid_.value(), desired_uid_, signal) == 0)
return;
// If we failed to kill the process group (maybe it doesn't exist yet because
// the forked process hasn't had a chance to call setsid()), just kill the
// child directly. If it hasn't called setsid() yet, then it hasn't called
// setuid() either, so kill it as root instead of as |desired_uid_|.
system_->kill(pid_.value(), 0, signal);
}
void Subprocess::Kill(int signal) {
DCHECK(pid_.has_value());
system_->kill(pid_.value(), desired_uid_, signal);
}
pid_t Subprocess::GetPid() const {
return pid_.value_or(-1);
}
void Subprocess::ClearPid() {
pid_.reset();
}
}; // namespace login_manager