1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/process_singleton.h"
11 #include <sys/types.h>
20 #include "base/command_line.h"
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.h"
23 #include "base/files/scoped_temp_dir.h"
24 #include "base/functional/bind.h"
25 #include "base/location.h"
26 #include "base/memory/raw_ptr.h"
27 #include "base/posix/eintr_wrapper.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/synchronization/waitable_event.h"
30 #include "base/task/single_thread_task_runner.h"
31 #include "base/test/metrics/histogram_tester.h"
32 #include "base/test/test_timeouts.h"
33 #include "base/test/thread_test_helper.h"
34 #include "base/threading/thread.h"
35 #include "base/time/time.h"
36 #include "build/build_config.h"
37 #include "chrome/common/chrome_constants.h"
38 #include "content/public/browser/browser_task_traits.h"
39 #include "content/public/browser/browser_thread.h"
40 #include "content/public/test/browser_task_environment.h"
41 #include "net/base/network_interfaces.h"
42 #include "testing/gtest/include/gtest/gtest.h"
46 class ProcessSingletonPosixTest : public testing::Test {
48 // A ProcessSingleton exposing some protected methods for testing.
49 class TestableProcessSingleton : public ProcessSingleton {
51 explicit TestableProcessSingleton(const base::FilePath& user_data_dir)
52 : ProcessSingleton(user_data_dir,
54 &TestableProcessSingleton::NotificationCallback,
55 base::Unretained(this))) {}
57 std::vector<base::CommandLine::StringVector> callback_command_lines_;
59 using ProcessSingleton::NotifyOtherProcessWithTimeout;
60 using ProcessSingleton::NotifyOtherProcessWithTimeoutOrCreate;
61 using ProcessSingleton::OverrideCurrentPidForTesting;
62 using ProcessSingleton::OverrideKillCallbackForTesting;
63 using ProcessSingleton::StartWatching;
66 bool NotificationCallback(const base::CommandLine& command_line,
67 const base::FilePath& current_directory) {
68 callback_command_lines_.push_back(command_line.argv());
73 ProcessSingletonPosixTest()
75 task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
76 wait_event_(base::WaitableEvent::ResetPolicy::MANUAL,
77 base::WaitableEvent::InitialState::NOT_SIGNALED),
78 signal_event_(base::WaitableEvent::ResetPolicy::MANUAL,
79 base::WaitableEvent::InitialState::NOT_SIGNALED),
80 process_singleton_on_thread_(nullptr) {}
82 void SetUp() override {
83 testing::Test::SetUp();
85 ProcessSingleton::DisablePromptForTesting();
86 ProcessSingleton::SkipIsChromeProcessCheckForTesting(false);
87 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(false);
88 // Put the lock in a temporary directory. Doesn't need to be a
89 // full profile to test this code.
90 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
91 // Use a long directory name to ensure that the socket isn't opened through
93 user_data_path_ = temp_dir_.GetPath().Append(
94 std::string(sizeof(sockaddr_un::sun_path), 'a'));
95 ASSERT_TRUE(CreateDirectory(user_data_path_));
97 lock_path_ = user_data_path_.Append(chrome::kSingletonLockFilename);
98 socket_path_ = user_data_path_.Append(chrome::kSingletonSocketFilename);
99 cookie_path_ = user_data_path_.Append(chrome::kSingletonCookieFilename);
102 void TearDown() override {
103 scoped_refptr<base::ThreadTestHelper> io_helper(
104 new base::ThreadTestHelper(content::GetIOThreadTaskRunner({}).get()));
105 ASSERT_TRUE(io_helper->Run());
107 // Destruct the ProcessSingleton object before the IO thread so that its
108 // internals are destructed properly.
109 if (process_singleton_on_thread_) {
110 worker_thread_->task_runner()->PostTask(
112 base::BindOnce(&ProcessSingletonPosixTest::DestructProcessSingleton,
113 base::Unretained(this)));
115 scoped_refptr<base::ThreadTestHelper> helper(
116 new base::ThreadTestHelper(worker_thread_->task_runner().get()));
117 ASSERT_TRUE(helper->Run());
120 testing::Test::TearDown();
123 void CreateProcessSingletonOnThread() {
124 ASSERT_FALSE(worker_thread_.get());
125 worker_thread_ = std::make_unique<base::Thread>("BlockingThread");
126 worker_thread_->Start();
128 worker_thread_->task_runner()->PostTask(
131 &ProcessSingletonPosixTest::CreateProcessSingletonInternal,
132 base::Unretained(this)));
134 scoped_refptr<base::ThreadTestHelper> helper(
135 new base::ThreadTestHelper(worker_thread_->task_runner().get()));
136 ASSERT_TRUE(helper->Run());
139 TestableProcessSingleton* CreateProcessSingleton() {
140 return new TestableProcessSingleton(user_data_path_);
145 ASSERT_EQ(0, lstat(lock_path_.value().c_str(), &statbuf));
146 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
148 ssize_t len = readlink(lock_path_.value().c_str(), buf, PATH_MAX);
151 ASSERT_EQ(0, lstat(socket_path_.value().c_str(), &statbuf));
152 ASSERT_TRUE(S_ISLNK(statbuf.st_mode));
154 len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
156 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
158 ASSERT_EQ(0, lstat(socket_target_path.value().c_str(), &statbuf));
159 ASSERT_TRUE(S_ISSOCK(statbuf.st_mode));
161 len = readlink(cookie_path_.value().c_str(), buf, PATH_MAX);
163 std::string cookie(buf, len);
165 base::FilePath remote_cookie_path = socket_target_path.DirName().
166 Append(chrome::kSingletonCookieFilename);
167 len = readlink(remote_cookie_path.value().c_str(), buf, PATH_MAX);
169 EXPECT_EQ(cookie, std::string(buf, len));
172 ProcessSingleton::NotifyResult NotifyOtherProcess(bool override_kill) {
173 std::unique_ptr<TestableProcessSingleton> process_singleton(
174 CreateProcessSingleton());
175 base::CommandLine command_line(
176 base::CommandLine::ForCurrentProcess()->GetProgram());
177 command_line.AppendArg("about:blank");
179 process_singleton->OverrideCurrentPidForTesting(
180 base::GetCurrentProcId() + 1);
181 process_singleton->OverrideKillCallbackForTesting(base::BindRepeating(
182 &ProcessSingletonPosixTest::KillCallback, base::Unretained(this)));
185 return process_singleton->NotifyOtherProcessWithTimeout(
186 command_line, kRetryAttempts, timeout(), true);
189 // A helper method to call ProcessSingleton::NotifyOtherProcessOrCreate().
190 ProcessSingleton::NotifyResult NotifyOtherProcessOrCreate(
191 const std::string& url) {
192 std::unique_ptr<TestableProcessSingleton> process_singleton(
193 CreateProcessSingleton());
194 base::CommandLine command_line(
195 base::CommandLine::ForCurrentProcess()->GetProgram());
196 command_line.AppendArg(url);
197 return process_singleton->NotifyOtherProcessWithTimeoutOrCreate(
198 command_line, kRetryAttempts, timeout());
201 void CheckNotified() {
202 ASSERT_TRUE(process_singleton_on_thread_);
203 ASSERT_EQ(1u, process_singleton_on_thread_->callback_command_lines_.size());
206 i < process_singleton_on_thread_->callback_command_lines_[0].size();
208 if (process_singleton_on_thread_->callback_command_lines_[0][i] ==
215 ASSERT_EQ(0, kill_callbacks_);
218 void BlockWorkerThread() {
219 worker_thread_->task_runner()->PostTask(
220 FROM_HERE, base::BindOnce(&ProcessSingletonPosixTest::BlockThread,
221 base::Unretained(this)));
224 void UnblockWorkerThread() {
225 wait_event_.Signal(); // Unblock the worker thread for shutdown.
226 signal_event_.Wait(); // Ensure thread unblocks before continuing.
231 signal_event_.Signal();
234 base::FilePath user_data_path_;
235 base::FilePath lock_path_;
236 base::FilePath socket_path_;
237 base::FilePath cookie_path_;
241 static const int kRetryAttempts = 2;
243 base::TimeDelta timeout() const {
244 return TestTimeouts::tiny_timeout() * kRetryAttempts;
247 void CreateProcessSingletonInternal() {
248 ASSERT_TRUE(!process_singleton_on_thread_);
249 process_singleton_on_thread_ = CreateProcessSingleton();
250 ASSERT_EQ(ProcessSingleton::PROCESS_NONE,
251 process_singleton_on_thread_->NotifyOtherProcessOrCreate());
252 process_singleton_on_thread_->StartWatching();
255 void DestructProcessSingleton() {
256 ASSERT_TRUE(process_singleton_on_thread_);
257 delete process_singleton_on_thread_;
260 void KillCallback(int pid) {
264 content::BrowserTaskEnvironment task_environment_;
265 base::ScopedTempDir temp_dir_;
266 base::WaitableEvent wait_event_;
267 base::WaitableEvent signal_event_;
269 std::unique_ptr<base::Thread> worker_thread_;
270 raw_ptr<TestableProcessSingleton, DanglingUntriaged>
271 process_singleton_on_thread_;
276 // Test if the socket file and symbol link created by ProcessSingletonPosix
278 // If this test flakes, use http://crbug.com/74554.
279 TEST_F(ProcessSingletonPosixTest, CheckSocketFile) {
280 CreateProcessSingletonOnThread();
284 // TODO(james.su@gmail.com): port following tests to Windows.
285 // Test success case of NotifyOtherProcess().
286 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessSuccess) {
287 CreateProcessSingletonOnThread();
288 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(true));
292 // Test failure case of NotifyOtherProcess().
293 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessFailure) {
294 base::HistogramTester histogram_tester;
295 CreateProcessSingletonOnThread();
298 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(true));
299 ASSERT_EQ(1, kill_callbacks_);
300 UnblockWorkerThread();
301 histogram_tester.ExpectUniqueSample(
302 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
303 ProcessSingleton::SOCKET_READ_FAILED, 1u);
306 // Test that we don't kill ourselves by accident if a lockfile with the same pid
308 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessNoSuicide) {
309 base::HistogramTester histogram_tester;
310 CreateProcessSingletonOnThread();
311 // Replace lockfile with one containing our own pid.
312 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
313 std::string symlink_content = base::StringPrintf(
315 net::GetHostName().c_str(),
317 base::GetCurrentProcId());
318 EXPECT_EQ(0, symlink(symlink_content.c_str(), lock_path_.value().c_str()));
320 // Remove socket so that we will not be able to notify the existing browser.
321 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
323 // Pretend we are browser process.
324 ProcessSingleton::SkipIsChromeProcessCheckForTesting(true);
326 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(false));
327 // If we've gotten to this point without killing ourself, the test succeeded.
328 histogram_tester.ExpectUniqueSample(
329 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
330 ProcessSingleton::SAME_BROWSER_INSTANCE, 1u);
333 // Test that we can still notify a process on the same host even after the
335 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessHostChanged) {
336 CreateProcessSingletonOnThread();
337 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
338 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
340 EXPECT_EQ(ProcessSingleton::PROCESS_NOTIFIED, NotifyOtherProcess(false));
344 // Test that we kill hung browser when lock says process is on another host and
345 // we can't notify it over the socket.
346 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessDifferingHost) {
347 base::HistogramTester histogram_tester;
348 CreateProcessSingletonOnThread();
352 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
353 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
355 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(true));
356 ASSERT_EQ(1, kill_callbacks_);
358 // lock_path_ should be unlinked in NotifyOtherProcess().
359 base::FilePath target_path;
360 EXPECT_FALSE(base::ReadSymbolicLink(lock_path_, &target_path));
362 UnblockWorkerThread();
364 histogram_tester.ExpectUniqueSample(
365 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
366 ProcessSingleton::SOCKET_READ_FAILED, 1u);
369 // Test that we'll start creating ProcessSingleton when we have old lock file
370 // that says process is on another host and there is browser with the same pid
371 // but with another user data dir. Also suppose that user opted to unlock
373 TEST_F(ProcessSingletonPosixTest,
374 NotifyOtherProcessDifferingHost_UnlockedProfileBeforeKill) {
375 base::HistogramTester histogram_tester;
376 CreateProcessSingletonOnThread();
380 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
381 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
383 // Remove socket so that we will not be able to notify the existing browser.
384 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
386 // Unlock profile that was locked by process on another host.
387 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(true);
388 // Treat process with pid 1234 as browser with different user data dir.
389 ProcessSingleton::SkipIsChromeProcessCheckForTesting(true);
391 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcess(false));
393 // lock_path_ should be unlinked in NotifyOtherProcess().
394 base::FilePath target_path;
395 EXPECT_FALSE(base::ReadSymbolicLink(lock_path_, &target_path));
397 UnblockWorkerThread();
399 histogram_tester.ExpectUniqueSample(
400 "Chrome.ProcessSingleton.RemoteHungProcessTerminateReason",
401 ProcessSingleton::NOTIFY_ATTEMPTS_EXCEEDED, 1u);
402 histogram_tester.ExpectUniqueSample(
403 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
404 ProcessSingleton::PROFILE_UNLOCKED_BEFORE_KILL, 1u);
407 // Test that we unlock profile when lock says process is on another host and we
408 // can't notify it over the socket.
409 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_DifferingHost) {
410 base::HistogramTester histogram_tester;
411 CreateProcessSingletonOnThread();
415 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
416 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
418 // Remove socket so that we will not be able to notify the existing browser.
419 EXPECT_EQ(0, unlink(socket_path_.value().c_str()));
420 // Unlock profile that was locked by process on another host.
421 ProcessSingleton::SetUserOptedUnlockInUseProfileForTesting(true);
423 std::string url("about:blank");
424 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcessOrCreate(url));
426 ASSERT_EQ(0, unlink(lock_path_.value().c_str()));
428 UnblockWorkerThread();
430 histogram_tester.ExpectUniqueSample(
431 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
432 ProcessSingleton::PROFILE_UNLOCKED, 1u);
435 // Test that Create fails when another browser is using the profile directory.
436 TEST_F(ProcessSingletonPosixTest, CreateFailsWithExistingBrowser) {
437 CreateProcessSingletonOnThread();
439 std::unique_ptr<TestableProcessSingleton> process_singleton(
440 CreateProcessSingleton());
441 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
442 EXPECT_FALSE(process_singleton->Create());
445 // Test that Create fails when another browser is using the profile directory
446 // but with the old socket location.
447 TEST_F(ProcessSingletonPosixTest, CreateChecksCompatibilitySocket) {
448 CreateProcessSingletonOnThread();
449 std::unique_ptr<TestableProcessSingleton> process_singleton(
450 CreateProcessSingleton());
451 process_singleton->OverrideCurrentPidForTesting(base::GetCurrentProcId() + 1);
453 // Do some surgery so as to look like the old configuration.
455 ssize_t len = readlink(socket_path_.value().c_str(), buf, sizeof(buf));
457 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
458 ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
459 ASSERT_EQ(0, rename(socket_target_path.value().c_str(),
460 socket_path_.value().c_str()));
461 ASSERT_EQ(0, unlink(cookie_path_.value().c_str()));
463 EXPECT_FALSE(process_singleton->Create());
466 // Test that we fail when lock says process is on another host and we can't
467 // notify it over the socket before of a bad cookie.
468 TEST_F(ProcessSingletonPosixTest, NotifyOtherProcessOrCreate_BadCookie) {
469 CreateProcessSingletonOnThread();
470 // Change the cookie.
471 EXPECT_EQ(0, unlink(cookie_path_.value().c_str()));
472 EXPECT_EQ(0, symlink("INCORRECTCOOKIE", cookie_path_.value().c_str()));
474 // Also change the hostname, so the remote does not retry.
475 EXPECT_EQ(0, unlink(lock_path_.value().c_str()));
476 EXPECT_EQ(0, symlink("FAKEFOOHOST-1234", lock_path_.value().c_str()));
478 std::string url("about:blank");
479 EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, NotifyOtherProcessOrCreate(url));
482 TEST_F(ProcessSingletonPosixTest, IgnoreSocketSymlinkWithTooLongTarget) {
483 base::HistogramTester histogram_tester;
484 CreateProcessSingletonOnThread();
485 // Change the symlink to one with a too-long target.
487 ssize_t len = readlink(socket_path_.value().c_str(), buf, PATH_MAX);
489 base::FilePath socket_target_path = base::FilePath(std::string(buf, len));
490 base::FilePath long_socket_target_path = socket_target_path.DirName().Append(
491 std::string(sizeof(sockaddr_un::sun_path), 'b'));
492 ASSERT_EQ(0, unlink(socket_path_.value().c_str()));
493 ASSERT_EQ(0, symlink(long_socket_target_path.value().c_str(),
494 socket_path_.value().c_str()));
496 // A new ProcessSingleton should ignore the invalid socket path target.
497 std::string url("about:blank");
498 EXPECT_EQ(ProcessSingleton::PROCESS_NONE, NotifyOtherProcessOrCreate(url));
500 // Lock file contains PID of unit_tests process. It is non browser process so
501 // we treat lock file as orphaned.
502 histogram_tester.ExpectUniqueSample(
503 "Chrome.ProcessSingleton.RemoteProcessInteractionResult",
504 ProcessSingleton::ORPHANED_LOCK_FILE, 1u);
507 #if BUILDFLAG(IS_MAC)
508 // Test that if there is an existing lock file, and we could not flock()
510 TEST_F(ProcessSingletonPosixTest, CreateRespectsOldMacLock) {
511 std::unique_ptr<TestableProcessSingleton> process_singleton(
512 CreateProcessSingleton());
513 base::ScopedFD lock_fd(HANDLE_EINTR(
514 open(lock_path_.value().c_str(), O_RDWR | O_CREAT | O_EXLOCK, 0644)));
515 ASSERT_TRUE(lock_fd.is_valid());
516 EXPECT_FALSE(process_singleton->Create());
517 base::File::Info info;
518 EXPECT_TRUE(base::GetFileInfo(lock_path_, &info));
519 EXPECT_FALSE(info.is_directory);
520 EXPECT_FALSE(info.is_symbolic_link);
523 // Test that if there is an existing lock file, and it's not locked, we replace
525 TEST_F(ProcessSingletonPosixTest, CreateReplacesOldMacLock) {
526 std::unique_ptr<TestableProcessSingleton> process_singleton(
527 CreateProcessSingleton());
528 EXPECT_TRUE(base::WriteFile(lock_path_, base::StringPiece()));
529 EXPECT_TRUE(process_singleton->Create());
532 #endif // BUILDFLAG(IS_MAC)