1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This test file is an evolution of
// chrome/notification_helper/notification_helper_process_unittest.cc. In
// addition to testing launching notification_helper.exe by the OS via registry
// which is what notification_helper_process_unittest is all about, this test
// also tests if chrome.exe can be successfully launched by
// notification_helper.exe via the NotificationActivator::Activate function.
//
// This test is compiled into unit_tests.exe rather than
// notification_helper_unittests.exe. This is because unit_tests.exe has data
// dependency on chrome.exe which is required by this test, and it's undesired
// to make notification_helper_unittests.exe have data dependency on chrome.exe.
#include <memory>
#include <string>
#include <NotificationActivationCallback.h>
#include <wrl/client.h>
#include "base/base_paths.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/process/process.h"
#include "base/process/process_iterator.h"
#include "base/test/test_timeouts.h"
#include "base/win/scoped_com_initializer.h"
#include "build/build_config.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item.h"
#include "chrome/installer/util/work_item_list.h"
#include "chrome/test/base/process_inspector_win.h"
#include "content/public/common/result_codes.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr wchar_t kLaunchId[] =
L"0|0|Default|0|https://example.com/|notification_id";
// Returns a handle to the process of id |pid| if it is an immediate child of
// |parent|.
base::Process OpenProcessIfChildOf(base::ProcessId pid,
const base::Process& parent) {
DCHECK(parent.IsValid());
// PROCESS_VM_READ access right is required for ProcessInspector::Create()
// below.
auto process = base::Process::OpenWithExtraPrivileges(pid);
if (!process.IsValid())
return process;
auto inspector = ProcessInspector::Create(process);
if (!inspector || inspector->GetParentPid() != parent.Pid())
process.Close();
return process;
}
// Used to filter all the descendant processes of the given process.
class ProcessTreeFilter : public base::ProcessFilter {
public:
explicit ProcessTreeFilter(base::Process process)
: parent_pid_(process.Pid()) {
ancestor_processes_[process.Pid()] = std::move(process);
}
ProcessTreeFilter(const ProcessTreeFilter&) = delete;
ProcessTreeFilter& operator=(const ProcessTreeFilter&) = delete;
bool Includes(const base::ProcessEntry& entry) const override {
auto iter = ancestor_processes_.find(entry.parent_pid());
if (iter != ancestor_processes_.end()) {
base::Process process = OpenProcessIfChildOf(entry.pid(), iter->second);
// If the process is invalid, it could be an immediate child of
// iter->second but has been killed resulting from its parent proc's being
// killed. Despite this, its child processes may not be killed yet. So in
// theory, we need to add its pid to ancestor_processes_ map and continue
// hunting for its descendant processes.
//
// However, it is possible that the pid was reused, and we don't want to
// kill the new proc's child processes. With this possibility, we choose
// not to kill the new process if it is invalid. This works fine for this
// test as chrome puts its sub-procs in a job object so that they all
// should die with the parent.
if (!process.IsValid())
return false;
has_child_process_alive_ = true;
ancestor_processes_[entry.pid()] = std::move(process);
return true;
}
return false;
}
bool has_child_process_alive() { return has_child_process_alive_; }
void set_has_child_process_alive(bool has_child_process_alive) {
has_child_process_alive_ = has_child_process_alive;
}
private:
// The handles of the ancestor processes, indexed by process id.
// Must be mutable because override function Includes() is const.
mutable base::flat_map<base::ProcessId, base::Process> ancestor_processes_;
// Id of the parent process.
const base::ProcessId parent_pid_;
// A flag indicating if there is any child process alive.
// Must be mutable because override function Includes() is const.
mutable bool has_child_process_alive_ = false;
};
// Kills |process| and all of its descendants. Child processes are explicitly
// killed to ensure that they do not outlive the test.
void KillProcessTree(base::Process process) {
ProcessTreeFilter process_tree_filter(process.Duplicate());
// Start by explicitly killing the main process.
ASSERT_TRUE(process.Terminate(content::RESULT_CODE_KILLED, true /* wait */));
// base::KillProcesses used in conjuction with KillProcessTree kills
// processes from parent to child. Loop until all descendant processes are
// killed with no more than kMaxTries tries.
static constexpr int kMaxTries = 10;
int num_tries = 0;
base::FilePath::StringType exe_name = installer::kChromeExe;
do {
process_tree_filter.set_has_child_process_alive(false);
base::KillProcesses(exe_name, content::RESULT_CODE_KILLED,
&process_tree_filter);
} while (process_tree_filter.has_child_process_alive() &&
++num_tries < kMaxTries);
DLOG_IF(ERROR, num_tries >= kMaxTries) << "Failed to kill all processes!";
}
// Returns the process with name |name| if it is found.
base::Process FindProcess(const std::wstring& name) {
unsigned int pid;
{
base::NamedProcessIterator iter(name, nullptr);
const auto* entry = iter.NextProcessEntry();
if (!entry)
return base::Process();
pid = entry->pid();
}
auto process = base::Process::Open(pid);
if (!process.IsValid())
return process;
// Since the process could go away suddenly before we open a handle to it,
// it's possible that a different process was just opened and assigned the
// same PID due to aggressive PID reuse. Now that a handle is held to *some*
// process, take another run through the snapshot to see if the process with
// this PID has the right exe name.
base::NamedProcessIterator iter(name, nullptr);
while (const auto* entry = iter.NextProcessEntry()) {
if (entry->pid() == pid)
return process; // PID was not reused since the PID's match.
}
return base::Process(); // The PID was reused.
}
// Used to filter all the immediate child processes by process id.
class ChildProcessFilter : public base::ProcessFilter {
public:
explicit ChildProcessFilter(base::ProcessId parent_pid)
: parent_pid_(parent_pid) {}
ChildProcessFilter(const ChildProcessFilter&) = delete;
ChildProcessFilter& operator=(const ChildProcessFilter&) = delete;
bool Includes(const base::ProcessEntry& entry) const override {
return parent_pid_ == entry.parent_pid();
}
private:
const base::ProcessId parent_pid_;
};
} // namespace
class NotificationHelperLaunchesChrome : public testing::Test {
public:
NotificationHelperLaunchesChrome(const NotificationHelperLaunchesChrome&) =
delete;
NotificationHelperLaunchesChrome& operator=(
const NotificationHelperLaunchesChrome&) = delete;
protected:
NotificationHelperLaunchesChrome() : root_(HKEY_CURRENT_USER) {}
~NotificationHelperLaunchesChrome() override = default;
void SetUp() override { ASSERT_NO_FATAL_FAILURE(RegisterServer()); }
void TearDown() override {
// The test creates a notification_helper process. When the test fails, this
// process and its child processes can be left behind. We should clean it up
// in this scenario.
base::Process process = FindProcess(installer::kNotificationHelperExe);
if (process.IsValid())
KillProcessTree(std::move(process));
ASSERT_NO_FATAL_FAILURE(UnregisterServer());
}
private:
// Registers notification_helper.exe as the server.
void RegisterServer() {
ASSERT_TRUE(scoped_com_initializer_.Succeeded());
// Notification_helper.exe is in the build output directory next to this
// test executable, as the test build target has a data_deps dependency on
// it.
base::FilePath dir_exe;
ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &dir_exe));
base::FilePath notification_helper_path =
dir_exe.Append(installer::kNotificationHelperExe);
work_item_list_ = base::WrapUnique(WorkItem::CreateWorkItemList());
installer::AddNativeNotificationWorkItems(root_, notification_helper_path,
work_item_list_.get());
ASSERT_TRUE(work_item_list_->Do());
}
// Unregisters the server by rolling back the work item list.
void UnregisterServer() {
if (work_item_list_)
work_item_list_->Rollback();
}
// Predefined handle to the registry.
const HKEY root_;
// A list of work items on the registry.
std::unique_ptr<WorkItemList> work_item_list_;
base::win::ScopedCOMInitializer scoped_com_initializer_;
};
TEST_F(NotificationHelperLaunchesChrome, ChromeLaunchTest) {
// There isn't a way to directly correlate the notification_helper.exe server
// to this test. So we need to hunt for the server.
base::Process notification_helper_process =
FindProcess(installer::kNotificationHelperExe);
ASSERT_FALSE(notification_helper_process.IsValid());
Microsoft::WRL::ComPtr<INotificationActivationCallback>
notification_activator;
ASSERT_HRESULT_SUCCEEDED(::CoCreateInstance(
install_static::GetToastActivatorClsid(), nullptr, CLSCTX_LOCAL_SERVER,
IID_PPV_ARGS(¬ification_activator)));
ASSERT_TRUE(notification_activator);
// The notification_helper server is now invoked upon the request of creating
// the object instance. The server module now holds a reference of the
// instance object, the notification_helper.exe process is alive waiting for
// that reference to be released.
notification_helper_process = FindProcess(installer::kNotificationHelperExe);
ASSERT_TRUE(notification_helper_process.IsValid());
// This relies on |notification_helper_process| outliving |filter| to ensure
// that its pid isn't reused.
ChildProcessFilter filter(notification_helper_process.Pid());
int child_chrome_process_count = 0;
base::Process notification_helper_crashpad;
{
base::NamedProcessIterator iter(installer::kChromeExe, &filter);
while (const auto* entry = iter.NextProcessEntry()) {
++child_chrome_process_count;
notification_helper_crashpad =
OpenProcessIfChildOf(entry->pid(), notification_helper_process);
}
}
// The notification_helper process has launched a child chrome process as its
// crashpad handler.
ASSERT_EQ(child_chrome_process_count, 1);
ASSERT_TRUE(notification_helper_crashpad.IsValid());
// Launch chrome.exe with the launch id from notification_helper.
ASSERT_HRESULT_SUCCEEDED(
notification_activator->Activate(L"", kLaunchId, nullptr, 0));
// Now the notification_helper process has another immediate child process, in
// addition to the crashpad child process as mentioned above. Note that
// notification_helper has more than two descendant chrome processes. Kill all
// notification_helper's child processes except for the crashpad child process
// while counting.
child_chrome_process_count = 0;
{
base::NamedProcessIterator iter(installer::kChromeExe, &filter);
while (const auto* entry = iter.NextProcessEntry()) {
if (entry->pid() == notification_helper_crashpad.Pid())
continue;
base::Process process =
OpenProcessIfChildOf(entry->pid(), notification_helper_process);
ASSERT_TRUE(process.IsValid());
KillProcessTree(std::move(process));
++child_chrome_process_count;
}
}
ASSERT_EQ(child_chrome_process_count, 1);
// The crashpad process should be the only living child process of
// notification_helper.
child_chrome_process_count = 0;
{
base::NamedProcessIterator iter(installer::kChromeExe, &filter);
while (iter.NextProcessEntry())
++child_chrome_process_count;
}
ASSERT_EQ(child_chrome_process_count, 1);
// Release the instance object. Now that the last (and the only) instance
// object of the module is released, the event living in the server
// process is signaled, which allows the notification_helper process and its
// crashpad child process to exit.
notification_activator.Reset();
ASSERT_TRUE(notification_helper_process.WaitForExitWithTimeout(
TestTimeouts::action_timeout(), nullptr));
ASSERT_TRUE(notification_helper_crashpad.WaitForExitWithTimeout(
TestTimeouts::action_timeout(), nullptr));
}
|