| // Copyright 2018 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "net-base/process_manager.h" | |
| #include <signal.h> | |
| #include <stdlib.h> | |
| #include <sys/prctl.h> | |
| #include <map> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/files/file_util.h> | |
| #include <base/functional/bind.h> | |
| #include <base/logging.h> | |
| #include <base/notreached.h> | |
| #include <base/posix/eintr_wrapper.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/string_util.h> | |
| #include <base/task/single_thread_task_runner.h> | |
| #include <base/time/time.h> | |
| namespace net_base { | |
| namespace { | |
| base::LazyInstance<ProcessManager>::DestructorAtExit g_process_manager = | |
| LAZY_INSTANCE_INITIALIZER; | |
| static const int kWaitpidPollTimesForSIGTERM = 10; | |
| static const int kWaitpidPollTimesForSIGKILL = 8; | |
| static const unsigned int kWaitpidPollIntervalUpperBoundMilliseconds = 2000; | |
| static const unsigned int kWaitpidPollInitialIntervalMilliseconds = 4; | |
| // The maximum size we keep for the stdout of a process. | |
| static constexpr int kMaxStdoutBufferSize = 32 * 1024; | |
| bool SetupChild(const std::map<std::string, std::string>& env, | |
| bool terminate_with_parent) { | |
| // Setup environment variables. | |
| clearenv(); | |
| for (const auto& key_value : env) { | |
| setenv(key_value.first.c_str(), key_value.second.c_str(), 0); | |
| } | |
| if (terminate_with_parent) { | |
| prctl(PR_SET_PDEATHSIG, SIGTERM); | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| ProcessManager::ProcessManager() = default; | |
| ProcessManager::~ProcessManager() = default; | |
| // static | |
| ProcessManager* ProcessManager::GetInstance() { | |
| return g_process_manager.Pointer(); | |
| } | |
| void ProcessManager::Init() { | |
| VLOG(2) << __func__; | |
| CHECK(!async_signal_handler_); | |
| async_signal_handler_ = std::make_unique<brillo::AsynchronousSignalHandler>(); | |
| async_signal_handler_->Init(); | |
| process_reaper_.Register(async_signal_handler_.get()); | |
| minijail_ = brillo::Minijail::GetInstance(); | |
| } | |
| void ProcessManager::Stop() { | |
| VLOG(2) << __func__; | |
| CHECK(async_signal_handler_); | |
| process_reaper_.Unregister(); | |
| async_signal_handler_.reset(); | |
| // Kill the pending termination processes (which we have already sent SIGTERM | |
| // to). Note that currently the process started by minijail will not be killed | |
| // automatically when the main process quits, so we need to do this manually. | |
| // See b/261666421 for an issue in the test which caused by this. | |
| for (const auto& [pid, callback] : pending_termination_processes_) { | |
| LOG(WARNING) << __func__ << ": send SIGKILL to " << pid; | |
| bool not_used; | |
| // Error will be logged in KillProcess(). | |
| KillProcess(pid, SIGKILL, ¬_used); | |
| } | |
| pending_termination_processes_.clear(); | |
| } | |
| pid_t ProcessManager::StartProcess( | |
| const base::Location& spawn_source, | |
| const base::FilePath& program, | |
| const std::vector<std::string>& arguments, | |
| const std::map<std::string, std::string>& environment, | |
| const std::vector<std::pair<int, int>>& fds_to_bind, | |
| bool terminate_with_parent, | |
| ExitCallback exit_callback) { | |
| VLOG(2) << __func__ << "(" << program.value() << ")"; | |
| // Setup/create child process. | |
| std::unique_ptr<brillo::Process> process(new brillo::ProcessImpl()); | |
| process->AddArg(program.value()); | |
| for (const auto& option : arguments) { | |
| process->AddArg(option); | |
| } | |
| for (const auto& pair : fds_to_bind) { | |
| process->BindFd(pair.first, pair.second); | |
| } | |
| // Important to close unused fds. See crbug.com/531655 and crbug.com/911234. | |
| process->SetCloseUnusedFileDescriptors(true); | |
| process->SetPreExecCallback( | |
| base::BindOnce(&SetupChild, environment, terminate_with_parent)); | |
| if (!process->Start()) { | |
| LOG(ERROR) << "Failed to start child process for " << program.value(); | |
| return kInvalidPID; | |
| } | |
| // Setup watcher for the child process. | |
| pid_t pid = process->pid(); | |
| CHECK(process_reaper_.WatchForChild( | |
| spawn_source, pid, | |
| base::BindOnce(&ProcessManager::OnProcessExited, | |
| weak_factory_.GetWeakPtr(), pid))); | |
| // Release ownership of the child process from the |process| object, so that | |
| // child process will not get killed on destruction of |process| object. | |
| process->Release(); | |
| watched_processes_[pid].exit_callback = std::move(exit_callback); | |
| return pid; | |
| } | |
| pid_t ProcessManager::StartProcessInMinijailWithPipes( | |
| const base::Location& spawn_source, | |
| const base::FilePath& program, | |
| const std::vector<std::string>& arguments, | |
| const std::map<std::string, std::string>& environment, | |
| const MinijailOptions& minijail_options, | |
| ExitCallback exit_callback, | |
| struct std_file_descriptors std_fds) { | |
| pid_t pid = StartProcessInMinijailWithPipesInternal( | |
| spawn_source, program, arguments, environment, minijail_options, std_fds); | |
| if (pid == kInvalidPID) { | |
| return pid; | |
| } | |
| watched_processes_[pid].exit_callback = std::move(exit_callback); | |
| return pid; | |
| } | |
| pid_t ProcessManager::StartProcessInMinijailWithStdout( | |
| const base::Location& spawn_source, | |
| const base::FilePath& program, | |
| const std::vector<std::string>& arguments, | |
| const std::map<std::string, std::string>& environment, | |
| const MinijailOptions& minijail_options, | |
| ExitWithStdoutCallback exit_callback) { | |
| int stdout_fd; | |
| pid_t pid = StartProcessInMinijailWithPipesInternal( | |
| spawn_source, program, arguments, environment, minijail_options, | |
| { | |
| .stdin_fd = nullptr, | |
| .stdout_fd = &stdout_fd, | |
| .stderr_fd = nullptr, | |
| }); | |
| if (pid == kInvalidPID) { | |
| return pid; | |
| } | |
| auto& watched_process = watched_processes_[pid]; | |
| watched_process.exit_with_stdout_callback = std::move(exit_callback); | |
| watched_process.stdout_fd.reset(stdout_fd); | |
| watched_process.stdout_watcher = base::FileDescriptorWatcher::WatchReadable( | |
| stdout_fd, base::BindRepeating(&ProcessManager::OnProcessStdoutReadable, | |
| weak_factory_.GetWeakPtr(), pid)); | |
| return pid; | |
| } | |
| bool ProcessManager::StopProcess(pid_t pid) { | |
| VLOG(2) << __func__ << "(" << pid << ")"; | |
| if (pending_termination_processes_.find(pid) != | |
| pending_termination_processes_.end()) { | |
| LOG(ERROR) << "Process " << pid << " already being stopped."; | |
| return false; | |
| } | |
| if (watched_processes_.find(pid) == watched_processes_.end()) { | |
| LOG(ERROR) << "Process " << pid << " not being watched"; | |
| return false; | |
| } | |
| // Caller not interested in watching this process anymore, since the | |
| // process termination is initiated by the caller. | |
| watched_processes_.erase(pid); | |
| // Attempt to send SIGTERM signal first. | |
| return TerminateProcess(pid, false); | |
| } | |
| bool ProcessManager::StopProcessAndBlock(pid_t pid) { | |
| VLOG(2) << __func__ << "(" << pid << ")"; | |
| auto terminated_process = pending_termination_processes_.find(pid); | |
| if (terminated_process != pending_termination_processes_.end()) { | |
| LOG(INFO) << "Process " << pid << " already being stopped."; | |
| terminated_process->second->Cancel(); | |
| pending_termination_processes_.erase(terminated_process); | |
| } else { | |
| if (watched_processes_.find(pid) == watched_processes_.end()) { | |
| LOG(ERROR) << "Process " << pid << " not being watched"; | |
| return false; | |
| } | |
| // Caller not interested in watching this process anymore, since the | |
| // process termination is initiated by the caller. | |
| watched_processes_.erase(pid); | |
| } | |
| // We are no longer interested in tracking the exit of this process. | |
| // Also, we will hopefully reap this process ourselves, so remove any | |
| // record of this pid from process_reaper_. | |
| process_reaper_.ForgetChild(pid); | |
| // Try SIGTERM firstly. | |
| // Send SIGKILL signal if SIGTERM was not handled in a timely manner. | |
| if (KillProcessWithTimeout(pid, false) || KillProcessWithTimeout(pid, true)) { | |
| return true; | |
| } | |
| // In case of killing failure. | |
| LOG(ERROR) << "Timeout waiting for process " << pid << " to be killed."; | |
| return false; | |
| } | |
| bool ProcessManager::KillProcessWithTimeout(pid_t pid, bool kill_signal) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| bool killed = false; | |
| if (KillProcess(pid, kill_signal ? SIGKILL : SIGTERM, &killed)) { | |
| if (killed) { | |
| return true; | |
| } | |
| int poll_times = | |
| kill_signal ? kWaitpidPollTimesForSIGKILL : kWaitpidPollTimesForSIGTERM; | |
| if (WaitpidWithTimeout(pid, kWaitpidPollInitialIntervalMilliseconds, | |
| kWaitpidPollIntervalUpperBoundMilliseconds, | |
| poll_times)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool ProcessManager::KillProcess(pid_t pid, int signal, bool* killed) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| if (kill(pid, signal) < 0) { | |
| if (errno == ESRCH) { | |
| VLOG(2) << "Process " << pid << " has exited."; | |
| *killed = true; | |
| return true; | |
| } | |
| PLOG(ERROR) << "Failed to send " << signal << "signal to process " << pid; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool ProcessManager::WaitpidWithTimeout(pid_t pid, | |
| unsigned int sleep_ms, | |
| unsigned int upper_bound_ms, | |
| int tries) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| while (tries-- > 0) { | |
| if (waitpid(pid, nullptr, WNOHANG) == pid) { | |
| return true; | |
| } | |
| usleep(sleep_ms * 1000); | |
| if (2 * sleep_ms < upper_bound_ms) { | |
| sleep_ms *= 2; | |
| } | |
| } | |
| return false; | |
| } | |
| bool ProcessManager::UpdateExitCallback(pid_t pid, ExitCallback new_callback) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| const auto process_entry = watched_processes_.find(pid); | |
| if (process_entry == watched_processes_.end()) { | |
| LOG(ERROR) << "Process " << pid << " not being watched"; | |
| return false; | |
| } | |
| process_entry->second.exit_callback = std::move(new_callback); | |
| return true; | |
| } | |
| pid_t ProcessManager::StartProcessInMinijailWithPipesInternal( | |
| const base::Location& spawn_source, | |
| const base::FilePath& program, | |
| const std::vector<std::string>& arguments, | |
| const std::map<std::string, std::string>& environment, | |
| const MinijailOptions& minijail_options, | |
| struct std_file_descriptors std_fds) { | |
| VLOG(2) << __func__ << "(" << program.value() << ")"; | |
| std::vector<char*> args; | |
| args.push_back(const_cast<char*>(program.value().c_str())); | |
| for (const auto& arg : arguments) { | |
| args.push_back(const_cast<char*>(arg.c_str())); | |
| } | |
| args.push_back(nullptr); | |
| std::vector<std::string> env_strings; | |
| for (const auto& var : environment) { | |
| env_strings.push_back( | |
| base::StringPrintf("%s=%s", var.first.c_str(), var.second.c_str())); | |
| } | |
| std::vector<char*> env; | |
| for (const auto& str : env_strings) { | |
| env.push_back(const_cast<char*>(str.c_str())); | |
| } | |
| env.push_back(nullptr); | |
| struct minijail* jail = minijail_->New(); | |
| if (!minijail_->DropRoot(jail, minijail_options.user.c_str(), | |
| minijail_options.group.c_str())) { | |
| LOG(ERROR) << "Minijail failed to drop root privileges?"; | |
| return kInvalidPID; | |
| } | |
| if (minijail_options.inherit_supplementary_groups) { | |
| minijail_inherit_usergroups(jail); | |
| } | |
| minijail_->UseCapabilities(jail, minijail_options.capmask); | |
| minijail_->ResetSignalMask(jail); | |
| minijail_->PreserveFd(jail, STDIN_FILENO, STDIN_FILENO); | |
| minijail_->PreserveFd(jail, STDOUT_FILENO, STDOUT_FILENO); | |
| minijail_->PreserveFd(jail, STDERR_FILENO, STDERR_FILENO); | |
| for (int fd : minijail_options.preserved_nonstd_fds) { | |
| minijail_->PreserveFd(jail, fd, fd); | |
| } | |
| // Important to close non-standard fds. See b/286322349, crbug.com/531655, | |
| // crbug.com/911234, crbug.com/914444. | |
| minijail_->CloseOpenFds(jail); | |
| if (minijail_options.rlimit_as_soft.has_value()) { | |
| minijail_rlimit(jail, RLIMIT_AS, minijail_options.rlimit_as_soft.value(), | |
| RLIM_INFINITY); | |
| } | |
| pid_t pid; | |
| if (!minijail_->RunEnvPipesAndDestroy(jail, args, env, &pid, std_fds.stdin_fd, | |
| std_fds.stdout_fd, std_fds.stderr_fd)) { | |
| LOG(ERROR) << "Unable to spawn " << program.value() << " in a jail."; | |
| return kInvalidPID; | |
| } | |
| CHECK(process_reaper_.WatchForChild( | |
| spawn_source, pid, | |
| base::BindOnce(&ProcessManager::OnProcessExited, | |
| weak_factory_.GetWeakPtr(), pid))); | |
| return pid; | |
| } | |
| void ProcessManager::OnProcessStdoutReadable(pid_t pid) { | |
| auto watched_process_it = watched_processes_.find(pid); | |
| if (watched_process_it == watched_processes_.end()) { | |
| LOG(ERROR) << __func__ << " called but process " << pid | |
| << " is not being tracked."; | |
| return; | |
| } | |
| int fd = watched_process_it->second.stdout_fd.get(); | |
| // PIPE_BUF is defined in the kernel, which specifies the maximum size of a | |
| // atomic write to a pipe. The value is 4KB in Linux. | |
| constexpr int kBufferSize = PIPE_BUF; | |
| char buffer[kBufferSize]; | |
| while (true) { | |
| ssize_t ret = HANDLE_EINTR(read(fd, buffer, kBufferSize)); | |
| if (ret == -1 && errno == EAGAIN) { | |
| // The pipe is empty now. Waits for more data. | |
| return; | |
| } else if (ret == 0 || ret == -1) { | |
| // 0 means we got an EOF, -1 means we got a real error. In both cases, the | |
| // read is done and we should check if the callback should be invoked. | |
| if (ret == -1) { | |
| PLOG(ERROR) << "Failed to read stdout for process " << pid; | |
| } | |
| watched_process_it->second.stdout_watcher = nullptr; | |
| watched_process_it->second.stdout_fd.reset(); | |
| CheckProcessExitStateAndNotify(pid); | |
| return; | |
| } else { | |
| // We got some data. | |
| watched_process_it->second.stdout_str.append(buffer, | |
| static_cast<size_t>(ret)); | |
| if (watched_process_it->second.stdout_str.size() > kMaxStdoutBufferSize) { | |
| watched_process_it->second.stdout_str.resize(kMaxStdoutBufferSize); | |
| } | |
| } | |
| } | |
| } | |
| void ProcessManager::OnProcessExited(pid_t pid, const siginfo_t& info) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| // Invoke the exit callback if the process is being watched. | |
| auto watched_process = watched_processes_.find(pid); | |
| if (watched_process != watched_processes_.end()) { | |
| watched_process->second.exit_status = info.si_status; | |
| CheckProcessExitStateAndNotify(pid); | |
| return; | |
| } | |
| // Process terminated by us, cancel timeout handler. | |
| auto terminated_process = pending_termination_processes_.find(pid); | |
| if (terminated_process != pending_termination_processes_.end()) { | |
| terminated_process->second->Cancel(); | |
| pending_termination_processes_.erase(terminated_process); | |
| return; | |
| } | |
| NOTREACHED() << "Unknown process " << pid << " status " << info.si_status; | |
| } | |
| void ProcessManager::CheckProcessExitStateAndNotify(pid_t pid) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| auto watched_process_it = watched_processes_.find(pid); | |
| CHECK(watched_process_it != watched_processes_.end()); | |
| const auto& process = watched_process_it->second; | |
| if (!process.exit_callback.is_null()) { | |
| if (!process.exit_status.has_value()) { | |
| LOG(DFATAL) << __func__ << " called but |exit_status| is empty"; | |
| return; | |
| } | |
| ExitCallback callback = std::move(watched_process_it->second.exit_callback); | |
| std::move(callback).Run(process.exit_status.value()); | |
| watched_processes_.erase(watched_process_it); | |
| return; | |
| } | |
| CHECK(!process.exit_with_stdout_callback.is_null()); | |
| if (process.exit_status.has_value() && !process.stdout_fd.is_valid()) { | |
| ExitWithStdoutCallback callback = | |
| std::move(watched_process_it->second.exit_with_stdout_callback); | |
| std::move(callback).Run(process.exit_status.value(), process.stdout_str); | |
| watched_processes_.erase(watched_process_it); | |
| } | |
| } | |
| void ProcessManager::ProcessTerminationTimeoutHandler(pid_t pid, | |
| bool kill_signal) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ")"; | |
| CHECK(pending_termination_processes_.find(pid) != | |
| pending_termination_processes_.end()); | |
| pending_termination_processes_.erase(pid); | |
| // Process still not killed after SIGKILL signal. | |
| if (kill_signal) { | |
| LOG(ERROR) << "Timeout waiting for process " << pid << " to be killed."; | |
| return; | |
| } | |
| // Retry using SIGKILL signal. | |
| TerminateProcess(pid, true); | |
| } | |
| bool ProcessManager::TerminateProcess(pid_t pid, bool kill_signal) { | |
| VLOG(2) << __func__ << "(pid: " << pid << ", " | |
| << "use_sigkill: " << kill_signal << ")"; | |
| int signal = (kill_signal) ? SIGKILL : SIGTERM; | |
| bool killed = false; | |
| if (!KillProcess(pid, signal, &killed)) { | |
| return false; | |
| } | |
| if (killed) { | |
| return true; | |
| } | |
| auto termination_callback = std::make_unique<TerminationTimeoutCallback>( | |
| base::BindOnce(&ProcessManager::ProcessTerminationTimeoutHandler, | |
| weak_factory_.GetWeakPtr(), pid, kill_signal)); | |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | |
| FROM_HERE, termination_callback->callback(), kTerminationTimeout); | |
| pending_termination_processes_[pid] = std::move(termination_callback); | |
| return true; | |
| } | |
| std::optional<bool> ProcessManager::IsTerminating( | |
| const base::FilePath& pid_path) { | |
| if (!base::PathExists(pid_path)) { | |
| return false; | |
| } | |
| std::string filepid; | |
| if (!base::ReadFileToString(pid_path, &filepid)) { | |
| LOG(ERROR) << "Failed to read the pid file"; | |
| return std::nullopt; | |
| } | |
| if (!base::TrimString(filepid, "\n", &filepid)) { | |
| LOG(ERROR) << "Failed to get the pid: current filepid value is " << filepid; | |
| return std::nullopt; | |
| } | |
| pid_t pid; | |
| if (!base::StringToInt(filepid, &pid)) { | |
| LOG(ERROR) << "Failed to change type of the pid value from string to int: " | |
| "current filepid value is " | |
| << filepid; | |
| return std::nullopt; | |
| } | |
| return pending_termination_processes_.find(pid) != | |
| pending_termination_processes_.end(); | |
| } | |
| } // namespace net_base |