| // 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 <stddef.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 |