blob: 45631649b7c99d9b1a600fe9c6d2d926a74616fd [file] [log] [blame]
// Copyright (c) 2014 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 "components/browser_watcher/watcher_client_win.h"
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/process/kill.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "components/browser_watcher/exit_code_watcher_win.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace browser_watcher {
namespace {
// Command line switches used to communiate to the child test.
const char kParentHandle[] = "parent-handle";
const char kLeakHandle[] = "leak-handle";
const char kNoLeakHandle[] = "no-leak-handle";
bool IsValidParentProcessHandle(base::CommandLine& cmd_line,
const char* switch_name) {
std::string str_handle =
cmd_line.GetSwitchValueASCII(switch_name);
size_t integer_handle = 0;
if (!base::StringToSizeT(str_handle, &integer_handle))
return false;
base::ProcessHandle handle =
reinterpret_cast<base::ProcessHandle>(integer_handle);
// Verify that we can get the associated process id.
base::ProcessId parent_id = base::GetProcId(handle);
if (parent_id == 0) {
// Unable to get the parent pid - perhaps insufficient permissions.
return false;
}
// Make sure the handle grants SYNCHRONIZE by waiting on it.
DWORD err = ::WaitForSingleObject(handle, 0);
if (err != WAIT_OBJECT_0 && err != WAIT_TIMEOUT) {
// Unable to wait on the handle - perhaps insufficient permissions.
return false;
}
return true;
}
std::string HandleToString(HANDLE handle) {
// A HANDLE is a void* pointer, which is the same size as a size_t,
// so we can use reinterpret_cast<> on it.
size_t integer_handle = reinterpret_cast<size_t>(handle);
return base::SizeTToString(integer_handle);
}
MULTIPROCESS_TEST_MAIN(VerifyParentHandle) {
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
// Make sure we got a valid parent process handle from the watcher client.
if (!IsValidParentProcessHandle(*cmd_line, kParentHandle)) {
LOG(ERROR) << "Invalid or missing parent-handle.";
return 1;
}
// If in the legacy mode, we expect this second handle will leak into the
// child process. This mainly serves to verify that the legacy mode is
// getting tested.
if (cmd_line->HasSwitch(kLeakHandle) &&
!IsValidParentProcessHandle(*cmd_line, kLeakHandle)) {
LOG(ERROR) << "Parent process handle unexpectedly didn't leak.";
return 1;
}
// If not in the legacy mode, this second handle should not leak into the
// child process.
if (cmd_line->HasSwitch(kNoLeakHandle) &&
IsValidParentProcessHandle(*cmd_line, kLeakHandle)) {
LOG(ERROR) << "Parent process handle unexpectedly leaked.";
return 1;
}
return 0;
}
class WatcherClientTest : public base::MultiProcessTest {
public:
void SetUp() override {
// Open an inheritable handle on our own process to test handle leakage.
self_.Set(::OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
TRUE, // Ineritable handle.
base::GetCurrentProcId()));
ASSERT_TRUE(self_.IsValid());
}
enum HandlePolicy {
LEAK_HANDLE,
NO_LEAK_HANDLE
};
// Get a base command line to launch back into this test fixture.
base::CommandLine GetBaseCommandLine(HandlePolicy handle_policy,
HANDLE parent_handle) {
base::CommandLine ret = base::GetMultiProcessTestChildBaseCommandLine();
ret.AppendSwitchASCII(switches::kTestChildProcess, "VerifyParentHandle");
ret.AppendSwitchASCII(kParentHandle, HandleToString(parent_handle));
switch (handle_policy) {
case LEAK_HANDLE:
ret.AppendSwitchASCII(kLeakHandle, HandleToString(self_.Get()));
break;
case NO_LEAK_HANDLE:
ret.AppendSwitchASCII(kNoLeakHandle, HandleToString(self_.Get()));
break;
default:
ADD_FAILURE() << "Impossible handle_policy";
}
return ret;
}
WatcherClient::CommandLineGenerator GetBaseCommandLineGenerator(
HandlePolicy handle_policy) {
return base::Bind(&WatcherClientTest::GetBaseCommandLine,
base::Unretained(this), handle_policy);
}
void AssertSuccessfulExitCode(base::Process process) {
ASSERT_TRUE(process.IsValid());
int exit_code = 0;
if (!process.WaitForExit(&exit_code))
FAIL() << "Process::WaitForExit failed.";
ASSERT_EQ(0, exit_code);
}
// Inheritable process handle used for testing.
base::win::ScopedHandle self_;
};
} // namespace
// TODO(siggi): More testing - test WatcherClient base implementation.
TEST_F(WatcherClientTest, LaunchWatcherSucceeds) {
// We can only use the non-legacy launch method on Windows Vista or better.
if (base::win::GetVersion() < base::win::VERSION_VISTA)
return;
WatcherClient client(GetBaseCommandLineGenerator(NO_LEAK_HANDLE));
ASSERT_FALSE(client.use_legacy_launch());
client.LaunchWatcher();
ASSERT_NO_FATAL_FAILURE(
AssertSuccessfulExitCode(client.process().Duplicate()));
}
TEST_F(WatcherClientTest, LaunchWatcherLegacyModeSucceeds) {
// Test the XP-compatible legacy launch mode. This is expected to leak
// a handle to the child process.
WatcherClient client(GetBaseCommandLineGenerator(LEAK_HANDLE));
// Use the legacy launch mode.
client.set_use_legacy_launch(true);
client.LaunchWatcher();
ASSERT_NO_FATAL_FAILURE(
AssertSuccessfulExitCode(client.process().Duplicate()));
}
TEST_F(WatcherClientTest, LegacyModeDetectedOnXP) {
// This test only works on Windows XP.
if (base::win::GetVersion() > base::win::VERSION_XP)
return;
// Test that the client detects the need to use legacy launch mode, and that
// it works on Windows XP.
WatcherClient client(GetBaseCommandLineGenerator(LEAK_HANDLE));
ASSERT_TRUE(client.use_legacy_launch());
client.LaunchWatcher();
ASSERT_NO_FATAL_FAILURE(
AssertSuccessfulExitCode(client.process().Duplicate()));
}
} // namespace browser_watcher