blob: db4e5ca2569976f0dc75332c650fb56ae21c9156 [file] [log] [blame]
// Copyright 2018 The Chromium 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 "chrome/chrome_cleaner/ipc/ipc_test_util.h"
#include <utility>
#include <vector>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "chrome/chrome_cleaner/ipc/sandbox.h"
#include "chrome/chrome_cleaner/logging/scoped_logging.h"
#include "sandbox/win/src/sandbox_factory.h"
namespace chrome_cleaner {
namespace {
constexpr char kMojoPipeTokenSwitch[] = "mojo-pipe-token";
constexpr wchar_t kIPCTestUtilLogSuffix[] = L"ipc-test-util";
class MojoSandboxSetupHooks : public SandboxSetupHooks {
public:
explicit MojoSandboxSetupHooks(SandboxedParentProcess* parent_process)
: parent_process_(parent_process) {}
~MojoSandboxSetupHooks() override = default;
// SandboxSetupHooks
ResultCode UpdateSandboxPolicy(sandbox::TargetPolicy* policy,
base::CommandLine* command_line) override {
base::HandlesToInheritVector handles_to_inherit;
parent_process_->CreateMojoPipe(command_line, &handles_to_inherit);
for (HANDLE handle : handles_to_inherit)
policy->AddHandleToShare(handle);
return RESULT_CODE_SUCCESS;
}
ResultCode TargetSpawned(
const base::Process& target_process,
const base::win::ScopedHandle& target_thread) override {
parent_process_->ConnectMojoPipe(target_process.Duplicate());
return RESULT_CODE_SUCCESS;
}
private:
SandboxedParentProcess* parent_process_;
};
base::FilePath GetLogPath() {
return ScopedLogging::GetLogFilePath(kIPCTestUtilLogSuffix);
}
bool DeleteChildProcessLogs() {
// Delete the child process log file if existing.
const base::FilePath log_path = GetLogPath();
if (!base::DeleteFile(log_path, false)) {
LOG(ERROR) << "Can't delete log file from previous run: "
<< log_path.value();
return false;
}
return true;
}
void PrintChildProcessLogs() {
const base::FilePath log_path = GetLogPath();
if (log_path.empty()) {
LOG(ERROR) << "Child process log path is empty";
return;
}
if (!base::PathExists(log_path)) {
LOG(ERROR) << "Child process log file doesn't exist";
return;
}
// Collect the child process log file, and dump the contents, to help
// debugging failures.
std::string log_file_contents;
if (!base::ReadFileToString(log_path, &log_file_contents)) {
LOG(ERROR) << "Failed to read child process log file";
return;
}
std::vector<base::StringPiece> lines =
base::SplitStringPiece(log_file_contents, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
LOG(ERROR) << "Dumping child process logs";
for (const auto& line : lines) {
LOG(ERROR) << "Child process: " << line;
}
}
} // namespace
ParentProcess::ParentProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)
: command_line_(base::GetMultiProcessTestChildBaseCommandLine()),
mojo_task_runner_(mojo_task_runner) {}
ParentProcess::~ParentProcess() {}
void ParentProcess::CreateImplOnIPCThread(
mojo::ScopedMessagePipeHandle mojo_pipe) {
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ParentProcess::CreateImpl, base::RetainedRef(this),
base::Passed(&mojo_pipe)));
}
void ParentProcess::DestroyImplOnIPCThread() {
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ParentProcess::DestroyImpl, base::RetainedRef(this)));
}
void ParentProcess::AppendSwitch(const std::string& switch_string) {
command_line_.AppendSwitch(switch_string);
}
void ParentProcess::AppendSwitch(const std::string& switch_string,
const std::string& value) {
command_line_.AppendSwitchASCII(switch_string, value);
}
void ParentProcess::AppendSwitchNative(const std::string& switch_string,
const base::string16& value) {
command_line_.AppendSwitchNative(switch_string, value);
}
void ParentProcess::AppendSwitchPath(const std::string& switch_string,
const base::FilePath& value) {
command_line_.AppendSwitchPath(switch_string, value);
}
bool ParentProcess::LaunchConnectedChildProcess(
const std::string& child_main_function,
int32_t* exit_code) {
return LaunchConnectedChildProcess(child_main_function,
TestTimeouts::action_timeout(), exit_code);
}
bool ParentProcess::LaunchConnectedChildProcess(
const std::string& child_main_function,
base::TimeDelta timeout,
int32_t* exit_code) {
if (!DeleteChildProcessLogs())
return false;
if (!PrepareAndLaunchTestChildProcess(child_main_function))
return false;
CreateImplOnIPCThread(std::move(mojo_pipe_));
const bool success = base::WaitForMultiprocessTestChildExit(
child_process_, timeout, exit_code);
if (!success) {
LOG(ERROR) << "Child process failed to terminate within " << timeout;
child_process_.Terminate(/*exit_code=*/-1, /*wait=*/false);
}
DestroyImplOnIPCThread();
if (!success || *exit_code != 0)
PrintChildProcessLogs();
return success;
}
bool ParentProcess::PrepareAndLaunchTestChildProcess(
const std::string& child_main_function) {
base::LaunchOptions launch_options;
CreateMojoPipe(&command_line_, &launch_options.handles_to_inherit);
base::Process child_process = base::SpawnMultiProcessTestChild(
child_main_function, command_line_, launch_options);
ConnectMojoPipe(std::move(child_process));
return true;
}
void ParentProcess::CreateMojoPipe(
base::CommandLine* command_line,
base::HandlesToInheritVector* handles_to_inherit) {
std::string mojo_pipe_token = base::NumberToString(base::RandUint64());
mojo_pipe_ = outgoing_invitation_.AttachMessagePipe(mojo_pipe_token);
command_line->AppendSwitchASCII(kMojoPipeTokenSwitch, mojo_pipe_token);
mojo_channel_.PrepareToPassRemoteEndpoint(handles_to_inherit, command_line);
}
void ParentProcess::ConnectMojoPipe(base::Process process) {
child_process_ = std::move(process);
mojo::OutgoingInvitation::Send(std::move(outgoing_invitation_),
child_process_.Handle(),
mojo_channel_.TakeLocalEndpoint());
}
scoped_refptr<MojoTaskRunner> ParentProcess::mojo_task_runner() {
return mojo_task_runner_;
}
SandboxedParentProcess::SandboxedParentProcess(
scoped_refptr<MojoTaskRunner> mojo_task_runner)
: ParentProcess(mojo_task_runner) {}
SandboxedParentProcess::~SandboxedParentProcess() {}
bool SandboxedParentProcess::PrepareAndLaunchTestChildProcess(
const std::string& child_main_function) {
MojoSandboxSetupHooks hooks(this);
// This switch usage is copied from SpawnMultiProcessTestChild.
//
// We can't use SpawnMultiProcessTestChild here, because it uses
// LaunchProcess internally and we need to start the test child using
// StartSandboxTarget, which uses BrokerServices::SpawnTarget.
if (!command_line_.HasSwitch(switches::kTestChildProcess))
command_line_.AppendSwitchASCII(switches::kTestChildProcess,
child_main_function);
chrome_cleaner::ResultCode result_code =
StartSandboxTarget(command_line_, &hooks, SandboxType::kTest);
if (result_code != RESULT_CODE_SUCCESS) {
LOG(ERROR) << "Failed to launch sandbox: " << result_code;
return false;
}
return true;
}
ChildProcess::ChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)
: mojo_task_runner_(mojo_task_runner),
command_line_(base::CommandLine::ForCurrentProcess()),
scopped_logging_(new ScopedLogging(kIPCTestUtilLogSuffix)) {
sandbox::TargetServices* target_services =
sandbox::SandboxFactory::GetTargetServices();
if (!target_services)
return;
sandbox::ResultCode result = target_services->Init();
if (result != sandbox::SBOX_ALL_OK) {
LOG(ERROR) << "Failed to initialize sandbox TargetServices: " << result;
return;
}
target_services_initialized_ = true;
}
ChildProcess::~ChildProcess() {}
void ChildProcess::LowerToken() const {
if (!target_services_initialized_)
return;
sandbox::TargetServices* target_services =
sandbox::SandboxFactory::GetTargetServices();
DCHECK(target_services);
target_services->LowerToken();
}
mojo::ScopedMessagePipeHandle ChildProcess::CreateMessagePipeFromCommandLine() {
auto channel_endpoint =
mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
*base::CommandLine::ForCurrentProcess());
auto incoming_invitation =
mojo::IncomingInvitation::Accept(std::move(channel_endpoint));
return incoming_invitation.ExtractMessagePipe(mojo_pipe_token());
}
std::string ChildProcess::mojo_pipe_token() const {
return command_line_->GetSwitchValueASCII(kMojoPipeTokenSwitch);
}
} // namespace chrome_cleaner