#include "chrome/browser/extensions/api/messaging/launch_context.h"
#include <windows.h>
#include <shellapi.h>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
#include "build/branding_buildflags.h"
#include "crypto/random.h"
#include "net/base/file_stream.h"
#include "net/base/net_errors.h"
namespace extensions {
namespace {
bool GetManifestPathWithFlagsFromSubkey(HKEY root_key,
DWORD flags,
const wchar_t* subkey,
const std::wstring& host_name,
std::wstring* result) {
base::win::RegKey key;
return key.Open(root_key, subkey, KEY_QUERY_VALUE | flags) == ERROR_SUCCESS &&
key.OpenKey(host_name.c_str(), KEY_QUERY_VALUE | flags) ==
ERROR_SUCCESS &&
key.ReadValue(nullptr, result) == ERROR_SUCCESS;
}
bool GetManifestPathWithFlags(HKEY root_key,
DWORD flags,
const std::wstring& host_name,
std::wstring* result) {
#if BUILDFLAG(CHROMIUM_BRANDING)
static constexpr wchar_t kChromiumNativeMessagingRegistryKey[] =
L"SOFTWARE\\Chromium\\NativeMessagingHosts";
if (GetManifestPathWithFlagsFromSubkey(root_key, flags,
kChromiumNativeMessagingRegistryKey,
host_name, result)) {
return true;
}
#endif
static constexpr wchar_t kChromeNativeMessagingRegistryKey[] =
L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts";
return GetManifestPathWithFlagsFromSubkey(
root_key, flags, kChromeNativeMessagingRegistryKey, host_name, result);
}
bool GetManifestPath(HKEY root_key,
const std::wstring& host_name,
std::wstring* result) {
return GetManifestPathWithFlags(root_key, KEY_WOW64_32KEY, host_name,
result) ||
GetManifestPathWithFlags(root_key, KEY_WOW64_64KEY, host_name, result);
}
base::Process LaunchNativeExeDirectly(const std::wstring& command,
base::LaunchOptions& options,
const std::wstring& in_pipe_name,
const std::wstring& out_pipe_name) {
SECURITY_ATTRIBUTES sa_attr = {};
sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sa_attr.bInheritHandle = TRUE;
sa_attr.lpSecurityDescriptor = nullptr;
base::win::ScopedHandle stdout_file(
::CreateFileW(out_pipe_name.c_str(), FILE_WRITE_DATA | SYNCHRONIZE, 0,
&sa_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
if (!stdout_file.is_valid()) {
LOG(ERROR) << "Failed to open write handle for stdout.";
return base::Process();
}
base::win::ScopedHandle stdin_file(
::CreateFileW(in_pipe_name.c_str(), FILE_READ_DATA | SYNCHRONIZE, 0,
&sa_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
if (!stdin_file.is_valid()) {
LOG(ERROR) << "Failed to open read handle for stdin.";
return base::Process();
}
options.stdin_handle = stdin_file.Get();
options.stdout_handle = stdout_file.Get();
options.stderr_handle = ::GetStdHandle(STD_ERROR_HANDLE);
options.handles_to_inherit.push_back(options.stdin_handle);
options.handles_to_inherit.push_back(options.stdout_handle);
if (options.stderr_handle != NULL) {
options.handles_to_inherit.push_back(options.stderr_handle);
}
return base::LaunchProcess(command, options);
}
base::Process LaunchNativeHostViaCmd(const std::wstring& command,
base::LaunchOptions& options,
const std::wstring& in_pipe_name,
const std::wstring& out_pipe_name) {
DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0);
if (comspec_length == 0) {
LOG(ERROR) << "COMSPEC is not set";
return base::Process();
}
std::wstring comspec;
::GetEnvironmentVariable(
L"COMSPEC", base::WriteInto(&comspec, comspec_length), comspec_length);
return base::LaunchProcess(
base::StrCat({comspec, L" /d /s /c \"", command, L"\" < ", in_pipe_name,
L" > ", out_pipe_name}),
options);
}
}
base::FilePath LaunchContext::FindManifest(const std::string& host_name,
bool allow_user_level_hosts,
std::string& error_message) {
std::wstring host_name_wide = base::UTF8ToWide(host_name);
std::wstring path_str;
bool found = false;
if (allow_user_level_hosts) {
found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str);
}
if (!found) {
found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str);
}
if (!found) {
error_message =
"Native messaging host " + host_name + " is not registered.";
return base::FilePath();
}
base::FilePath manifest_path(path_str);
if (!manifest_path.IsAbsolute()) {
error_message = "Path to native messaging host manifest must be absolute.";
return base::FilePath();
}
return manifest_path;
}
std::optional<LaunchContext::ProcessState> LaunchContext::LaunchNativeProcess(
const base::CommandLine& command_line,
bool native_hosts_executables_launch_directly) {
const DWORD kTimeoutMs = 5000;
const DWORD kBufferSize = 0;
if (!command_line.GetProgram().IsAbsolute()) {
LOG(ERROR) << "Native Messaging host path must be absolute.";
return std::nullopt;
}
uint64_t pipe_name_token;
crypto::RandBytes(base::byte_span_from_ref(pipe_name_token));
const std::wstring pipe_name_token_str =
base::ASCIIToWide(base::StringPrintf("%llx", pipe_name_token));
const std::wstring out_pipe_name =
L"\\\\.\\pipe\\chrome.nativeMessaging.out." + pipe_name_token_str;
const std::wstring in_pipe_name =
L"\\\\.\\pipe\\chrome.nativeMessaging.in." + pipe_name_token_str;
base::win::ScopedHandle stdout_pipe(::CreateNamedPipeW(
out_pipe_name.c_str(),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED |
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL));
if (!stdout_pipe.is_valid()) {
LOG(ERROR) << "Failed to create pipe " << out_pipe_name;
return std::nullopt;
}
base::win::ScopedHandle stdin_pipe(::CreateNamedPipeW(
in_pipe_name.c_str(),
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL));
if (!stdin_pipe.is_valid()) {
LOG(ERROR) << "Failed to create pipe " << in_pipe_name;
return std::nullopt;
}
std::wstring command = command_line.GetCommandLineString();
base::LaunchOptions options;
options.current_directory = command_line.GetProgram().DirName();
options.start_hidden = true;
const bool use_direct_launch =
native_hosts_executables_launch_directly &&
command_line.GetProgram().MatchesFinalExtension(L".exe");
base::Process launched_process;
if (use_direct_launch) {
uintptr_t exe_type = ::SHGetFileInfoW(
command_line.GetProgram().value().c_str(), 0, NULL, 0, SHGFI_EXETYPE);
if ((LOWORD(exe_type) == 0x4550) && (HIWORD(exe_type) != 0)) {
options.start_hidden = false;
}
launched_process =
LaunchNativeExeDirectly(command, options, in_pipe_name, out_pipe_name);
} else {
launched_process =
LaunchNativeHostViaCmd(command, options, in_pipe_name, out_pipe_name);
}
if (!launched_process.IsValid()) {
LOG(ERROR) << "Error launching process "
<< command_line.GetProgram().MaybeAsASCII();
return std::nullopt;
}
return ProcessState(std::move(launched_process), std::move(stdout_pipe),
std::move(stdin_pipe));
}
void LaunchContext::ConnectPipes(base::ScopedPlatformFile read_file,
base::ScopedPlatformFile write_file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(native_process_.IsValid());
read_stream_ = std::make_unique<net::FileStream>(
base::File(std::move(read_file), true),
background_task_runner_);
write_stream_ = std::make_unique<net::FileStream>(
base::File(std::move(write_file), true),
background_task_runner_);
const int read_result = read_stream_->ConnectNamedPipe(
base::BindOnce(&LaunchContext::OnReadStreamConnectResult, GetWeakPtr()));
const int write_result = write_stream_->ConnectNamedPipe(
base::BindOnce(&LaunchContext::OnWriteStreamConnectResult, GetWeakPtr()));
read_pipe_connected_ = read_result == net::OK;
write_pipe_connected_ = write_result == net::OK;
if ((!read_pipe_connected_ && read_result != net::ERR_IO_PENDING) ||
(!write_pipe_connected_ && write_result != net::ERR_IO_PENDING)) {
OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
return;
}
if (read_pipe_connected_ && write_pipe_connected_) {
OnSuccess(base::kInvalidPlatformFile, std::move(read_stream_),
std::move(write_stream_));
return;
}
process_watcher_.StartWatchingOnce(native_process_.Handle(), this);
}
void LaunchContext::OnReadStreamConnectResult(int net_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (net_error != net::OK) {
OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
return;
}
read_pipe_connected_ = true;
OnPipeConnected();
}
void LaunchContext::OnWriteStreamConnectResult(int net_error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (net_error != net::OK) {
OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
return;
}
write_pipe_connected_ = true;
OnPipeConnected();
}
void LaunchContext::OnPipeConnected() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!read_pipe_connected_ || !write_pipe_connected_) {
return;
}
process_watcher_.StopWatching();
OnSuccess(base::kInvalidPlatformFile, std::move(read_stream_),
std::move(write_stream_));
}
void LaunchContext::OnObjectSignaled(HANDLE object) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_EQ(object, native_process_.Handle());
int exit_code = 0;
if (native_process_.WaitForExitWithTimeout({}, &exit_code)) {
LOG(ERROR) << "Native Messaging host process exited with code "
<< exit_code;
} else {
LOG(ERROR) << "Native Messaging host process exited unexpectedly";
}
OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
}
}