blob: 0409ca4fca3957febd7354044a7cec841aeaa547 [file] [log] [blame]
// Copyright 2016 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 "authpolicy/process_executor.h"
#include <stdlib.h>
#include <algorithm>
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include <libminijail.h>
#include <scoped_minijail.h>
#include "authpolicy/anonymizer.h"
#include "authpolicy/log_colors.h"
#include "authpolicy/platform_helper.h"
#include "authpolicy/samba_helper.h"
namespace authpolicy {
namespace {
// Prevent some environment variables from being wiped since they're used by
// tests. crbug.com/718182.
struct EnvVarDef {
const char* name_equals; // "name="
size_t size; // strlen("name=")
};
// Note: strlen doesn't work with constexpr.
#define DEFINE_ENV_VAR(name) \
{ name "=", sizeof(name "=") - 1 }
constexpr EnvVarDef kWhitelistedEnvVars[]{
DEFINE_ENV_VAR("ASAN_OPTIONS"), DEFINE_ENV_VAR("LSAN_OPTIONS"),
DEFINE_ENV_VAR("MSAN_OPTIONS"), DEFINE_ENV_VAR("TSAN_OPTIONS"),
DEFINE_ENV_VAR("UBSAN_OPTIONS"),
};
#undef DEFINE_ENV_VAR
} // namespace
ProcessExecutor::ProcessExecutor(std::vector<std::string> args)
: args_(std::move(args)) {}
void ProcessExecutor::SetInputFile(int fd) {
input_fd_ = fd;
}
void ProcessExecutor::SetInputString(const std::string& input_str) {
input_str_ = input_str;
}
void ProcessExecutor::SetEnv(const std::string& key, const std::string& value) {
env_map_[key] = value;
}
void ProcessExecutor::SetSeccompFilter(const std::string& policy_file) {
seccomp_policy_file_ = policy_file;
}
void ProcessExecutor::LogSeccompFilterFailures(bool enabled) {
log_seccomp_failures_ = enabled;
}
void ProcessExecutor::SetNoNewPrivs(bool enabled) {
no_new_privs_ = enabled;
}
void ProcessExecutor::KeepSupplementaryGroups(bool enabled) {
keep_supplementary_flags_ = enabled;
}
void ProcessExecutor::LogCommand(bool enabled) {
log_command_ = enabled;
}
void ProcessExecutor::LogOutput(bool enabled) {
log_output_ = enabled;
}
void ProcessExecutor::LogOutputOnError(bool enabled) {
log_output_on_error_ = enabled;
}
void ProcessExecutor::SetAnonymizer(Anonymizer* anonymizer) {
anonymizer_ = anonymizer;
}
bool ProcessExecutor::Execute() {
ResetOutput();
if (args_.empty() || args_[0].empty())
return true;
bool need_anonymizer = log_command_ || log_output_ || log_output_on_error_;
CHECK(anonymizer_ || !need_anonymizer) << "Logs must be anonymized";
if (!base::FilePath(args_[0]).IsAbsolute()) {
LOG(ERROR) << "Command must be specified by absolute path.";
exit_code_ = kExitCodeInternalError;
return false;
}
if (log_command_ && LOG_IS_ON(INFO)) {
std::string cmd = args_[0];
for (size_t n = 1; n < args_.size(); ++n)
cmd += base::StringPrintf(" '%s'", args_[n].c_str());
LOG(INFO) << kColorCommand << "Executing " << anonymizer_->Process(cmd)
<< kColorReset;
}
// Convert args to array of pointers. Must be nullptr terminated.
std::vector<char*> args_ptr;
for (const auto& arg : args_)
args_ptr.push_back(const_cast<char*>(arg.c_str()));
args_ptr.push_back(nullptr);
// Save old environment and set ours. Note that clearenv() doesn't actually
// delete any pointers, so we can just keep the old pointers.
std::vector<char*> old_environ;
for (char** env = environ; env != nullptr && *env != nullptr; ++env)
old_environ.push_back(*env);
clearenv();
// Store strings in list because putenv requires pointers to stay alive.
std::vector<std::string> env_list;
for (const auto& env : env_map_) {
env_list.push_back(env.first + "=" + env.second);
putenv(const_cast<char*>(env_list.back().c_str()));
}
// Add back whitelisted env vars. Note that |whitelisted_var.name_equals| is
// name= and |env| is name=value. A linear search seems fine, but consider
// using a map if kWhitelistedEnvVars grows.
for (char* env : old_environ) {
for (const EnvVarDef& whitelisted_var : kWhitelistedEnvVars) {
if (strncmp(env, whitelisted_var.name_equals, whitelisted_var.size) == 0)
putenv(env);
}
}
// Prepare minijail.
ScopedMinijail jail(minijail_new());
if (log_seccomp_failures_)
minijail_log_seccomp_filter_failures(jail.get());
if (!seccomp_policy_file_.empty()) {
minijail_parse_seccomp_filters(jail.get(), seccomp_policy_file_.c_str());
minijail_use_seccomp_filter(jail.get());
}
if (no_new_privs_)
minijail_no_new_privs(jail.get());
if (keep_supplementary_flags_)
minijail_keep_supplementary_gids(jail.get());
// Execute the command.
pid_t pid = -1;
int child_stdin = -1, child_stdout = -1, child_stderr = -1;
minijail_run_pid_pipes(jail.get(), args_ptr[0], args_ptr.data(), &pid,
&child_stdin, &child_stdout, &child_stderr);
// Make sure the pipes never block.
if (!base::SetNonBlocking(child_stdin))
LOG(WARNING) << "Failed to set stdin non-blocking";
if (!base::SetNonBlocking(child_stdout))
LOG(WARNING) << "Failed to set stdout non-blocking";
if (!base::SetNonBlocking(child_stderr))
LOG(WARNING) << "Failed to set stderr non-blocking";
// Restore the environment.
clearenv();
for (char* env : old_environ)
putenv(env);
if (perform_pipe_io_after_process_exit_for_testing_)
exit_code_ = minijail_wait(jail.get());
// Write to child_stdin and read from child_stdout and child_stderr while
// there is still data to read/write.
bool io_success =
PerformPipeIo(child_stdin, child_stdout, child_stderr, input_fd_,
input_str_, &out_data_, &err_data_);
// Wait for the process to exit.
if (!perform_pipe_io_after_process_exit_for_testing_)
exit_code_ = minijail_wait(jail.get());
jail.reset();
// Print out a useful error message for seccomp failures.
if (exit_code_ == MINIJAIL_ERR_JAIL)
LOG(ERROR) << "Seccomp filter blocked a system call";
// Always exit AFTER minijail_wait! If we do it before, the exit code is never
// queried and the process is left dangling.
if (!io_success) {
LOG(ERROR) << "IO failed";
exit_code_ = kExitCodeInternalError;
return false;
}
output_logged_ = false;
if (log_output_ || (log_output_on_error_ && exit_code_ != 0))
LogOutputOnce();
LOG_IF(INFO, log_command_)
<< kColorCommand << "Exit code: " << exit_code_ << kColorReset;
return exit_code_ == 0;
}
void ProcessExecutor::LogOutputOnce() {
if (output_logged_ || args_.empty() || !(log_output_on_error_ || log_output_))
return;
LogLongString(kColorCommandStdout, args_[0] + " stdout: ", out_data_,
anonymizer_);
LogLongString(kColorCommandStderr, args_[0] + " stderr: ", err_data_,
anonymizer_);
output_logged_ = true;
}
void ProcessExecutor::SetPerformPipeIoAfterProcessExitForTesting(
bool perform_pipe_io_after_process_exit_for_testing) {
perform_pipe_io_after_process_exit_for_testing_ =
perform_pipe_io_after_process_exit_for_testing;
}
void ProcessExecutor::ResetOutput() {
exit_code_ = 0;
out_data_.clear();
err_data_.clear();
}
} // namespace authpolicy