OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "components/browser_watcher/watcher_client_win.h" |
| 6 |
| 7 #include <string> |
| 8 |
| 9 #include "base/base_switches.h" |
| 10 #include "base/command_line.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/process/process_handle.h" |
| 13 #include "base/process/kill.h" |
| 14 #include "base/strings/string_number_conversions.h" |
| 15 #include "base/strings/stringprintf.h" |
| 16 #include "base/test/multiprocess_test.h" |
| 17 #include "base/test/test_reg_util_win.h" |
| 18 #include "base/win/scoped_handle.h" |
| 19 #include "base/win/windows_version.h" |
| 20 #include "components/browser_watcher/exit_code_watcher_win.h" |
| 21 #include "testing/gtest/include/gtest/gtest.h" |
| 22 #include "testing/multiprocess_func_list.h" |
| 23 |
| 24 namespace browser_watcher { |
| 25 |
| 26 namespace { |
| 27 |
| 28 // Command line switches used to communiate to the child test. |
| 29 const char kParentPid[] = "parent-pid"; |
| 30 const char kLeakHandle[] = "leak-handle"; |
| 31 const char kNoLeakHandle[] = "no-leak-handle"; |
| 32 |
| 33 bool IsValidParentProcessHandle(base::CommandLine& cmd_line, |
| 34 const char* switch_name) { |
| 35 std::string str_handle = |
| 36 cmd_line.GetSwitchValueASCII(switch_name); |
| 37 |
| 38 unsigned int_handle = 0; |
| 39 if (!base::StringToUint(str_handle, &int_handle)) |
| 40 return false; |
| 41 |
| 42 int parent_pid = 0; |
| 43 if (!base::StringToInt(cmd_line.GetSwitchValueASCII(kParentPid), |
| 44 &parent_pid)) { |
| 45 return false; |
| 46 } |
| 47 |
| 48 base::ProcessHandle handle = |
| 49 reinterpret_cast<base::ProcessHandle>(int_handle); |
| 50 // Verify that we can get the associated process id. |
| 51 base::ProcessId parent_id = base::GetProcId(handle); |
| 52 if (parent_id == 0) { |
| 53 // Unable to get the parent pid - perhaps insufficient permissions. |
| 54 return false; |
| 55 } |
| 56 |
| 57 // Make sure the handle grants SYNCHRONIZE by waiting on it. |
| 58 DWORD err = ::WaitForSingleObject(handle, 0); |
| 59 if (err != WAIT_OBJECT_0 && err != WAIT_TIMEOUT) { |
| 60 // Unable to wait on the handle - perhaps insufficient permissions. |
| 61 return false; |
| 62 } |
| 63 |
| 64 return true; |
| 65 } |
| 66 |
| 67 MULTIPROCESS_TEST_MAIN(VerifyParentHandle) { |
| 68 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| 69 |
| 70 // Make sure we got a valid parent process handle from the watcher client. |
| 71 if (!IsValidParentProcessHandle(*cmd_line, |
| 72 ExitCodeWatcher::kParenthHandleSwitch)) { |
| 73 LOG(ERROR) << "Invalid or missing parent-handle."; |
| 74 return 1; |
| 75 } |
| 76 |
| 77 // If in the legacy mode, we expect this second handle will leak into the |
| 78 // child process. This mainly serves to verify that the legacy mode is |
| 79 // getting tested. |
| 80 if (cmd_line->HasSwitch(kLeakHandle) && |
| 81 !IsValidParentProcessHandle(*cmd_line, kLeakHandle)) { |
| 82 LOG(ERROR) << "Parent process handle unexpectedly didn't leak."; |
| 83 return 1; |
| 84 } |
| 85 |
| 86 // If not in the legacy mode, this second handle should not leak into the |
| 87 // child process. |
| 88 if (cmd_line->HasSwitch(kNoLeakHandle) && |
| 89 IsValidParentProcessHandle(*cmd_line, kLeakHandle)) { |
| 90 LOG(ERROR) << "Parent process handle unexpectedly leaked."; |
| 91 return 1; |
| 92 } |
| 93 |
| 94 return 0; |
| 95 } |
| 96 |
| 97 class BrowserWatcherClientTest : public base::MultiProcessTest { |
| 98 public: |
| 99 virtual void SetUp() { |
| 100 // Open an inheritable handle on our own process to test handle leakage. |
| 101 self_.Set(::OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, |
| 102 TRUE, // Ineritable handle. |
| 103 base::GetCurrentProcId())); |
| 104 |
| 105 ASSERT_TRUE(self_.IsValid()); |
| 106 } |
| 107 |
| 108 enum HandlePolicy { |
| 109 LEAK_HANDLE, |
| 110 NO_LEAK_HANDLE |
| 111 }; |
| 112 |
| 113 // Get a base command line to launch back into this test fixture. |
| 114 base::CommandLine GetBaseCommandLine(HandlePolicy handle_policy) { |
| 115 base::CommandLine ret = base::GetMultiProcessTestChildBaseCommandLine(); |
| 116 |
| 117 ret.AppendSwitchASCII(switches::kTestChildProcess, "VerifyParentHandle"); |
| 118 ret.AppendSwitchASCII(kParentPid, |
| 119 base::StringPrintf("%d", base::GetCurrentProcId())); |
| 120 |
| 121 switch (handle_policy) { |
| 122 case LEAK_HANDLE: |
| 123 ret.AppendSwitchASCII(kLeakHandle, |
| 124 base::StringPrintf("%d", self_.Get())); |
| 125 break; |
| 126 |
| 127 case NO_LEAK_HANDLE: |
| 128 ret.AppendSwitchASCII(kNoLeakHandle, |
| 129 base::StringPrintf("%d", self_.Get())); |
| 130 break; |
| 131 |
| 132 default: |
| 133 ADD_FAILURE() << "Impossible handle_policy"; |
| 134 } |
| 135 |
| 136 return ret; |
| 137 } |
| 138 |
| 139 void AssertSuccessfulExitCode(base::ProcessHandle handle) { |
| 140 ASSERT_NE(base::kNullProcessHandle, handle); |
| 141 |
| 142 // Duplicate the process handle to work around the fact that |
| 143 // WaitForExitCode closes it(!!!). |
| 144 base::ProcessHandle dupe = NULL; |
| 145 ASSERT_TRUE(::DuplicateHandle(base::GetCurrentProcessHandle(), |
| 146 handle, |
| 147 base::GetCurrentProcessHandle(), |
| 148 &dupe, |
| 149 SYNCHRONIZE | PROCESS_QUERY_INFORMATION, |
| 150 FALSE, |
| 151 0)); |
| 152 ASSERT_NE(base::kNullProcessHandle, dupe); |
| 153 int exit_code = 0; |
| 154 if (!base::WaitForExitCode(dupe, &exit_code)) { |
| 155 base::CloseProcessHandle(dupe); |
| 156 FAIL() << "WaitForExitCode failed."; |
| 157 } |
| 158 ASSERT_EQ(0, exit_code); |
| 159 } |
| 160 |
| 161 // Inheritable process handle used for testing. |
| 162 base::win::ScopedHandle self_; |
| 163 }; |
| 164 |
| 165 } // namespace |
| 166 |
| 167 // TODO(siggi): More testing - test WatcherClient base implementation. |
| 168 |
| 169 TEST_F(BrowserWatcherClientTest, LaunchWatcherSucceeds) { |
| 170 // We can only use the non-legacy launch method on Windows Vista or better. |
| 171 if (base::win::GetVersion() < base::win::VERSION_VISTA) |
| 172 return; |
| 173 |
| 174 WatcherClient client(GetBaseCommandLine(NO_LEAK_HANDLE)); |
| 175 ASSERT_FALSE(client.use_legacy_launch()); |
| 176 |
| 177 client.LaunchWatcher(); |
| 178 |
| 179 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client.process())); |
| 180 } |
| 181 |
| 182 TEST_F(BrowserWatcherClientTest, LaunchWatcherLegacyModeSucceeds) { |
| 183 // Test the XP-compatible legacy launch mode. This is expected to leak |
| 184 // a handle to the child process. |
| 185 WatcherClient client(GetBaseCommandLine(LEAK_HANDLE)); |
| 186 |
| 187 // Use the legacy launch mode. |
| 188 client.set_use_legacy_launch(true); |
| 189 |
| 190 client.LaunchWatcher(); |
| 191 |
| 192 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client.process())); |
| 193 } |
| 194 |
| 195 TEST_F(BrowserWatcherClientTest, LegacyModeDetectedOnXP) { |
| 196 // This test only works on Windows XP. |
| 197 if (base::win::GetVersion() > base::win::VERSION_XP) |
| 198 return; |
| 199 |
| 200 // Test that the client detects the need to use legacy launch mode, and that |
| 201 // it works on Windows XP. |
| 202 WatcherClient client(GetBaseCommandLine(LEAK_HANDLE)); |
| 203 ASSERT_TRUE(client.use_legacy_launch()); |
| 204 |
| 205 client.LaunchWatcher(); |
| 206 |
| 207 ASSERT_NO_FATAL_FAILURE(AssertSuccessfulExitCode(client.process())); |
| 208 } |
| 209 |
| 210 } // namespace browser_watcher |
OLD | NEW |