1 // Copyright (c) 2012 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 "chrome/installer/setup/setup_util_unittest.h" 6 7 #include <windows.h> 8 9 #include <string> 10 11 #include "base/command_line.h" 12 #include "base/files/file_util.h" 13 #include "base/files/scoped_temp_dir.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/process/kill.h" 16 #include "base/process/launch.h" 17 #include "base/process/process_handle.h" 18 #include "base/test/test_reg_util_win.h" 19 #include "base/threading/platform_thread.h" 20 #include "base/time/time.h" 21 #include "base/version.h" 22 #include "base/win/scoped_handle.h" 23 #include "base/win/windows_version.h" 24 #include "chrome/installer/setup/setup_constants.h" 25 #include "chrome/installer/setup/setup_util.h" 26 #include "chrome/installer/util/google_update_constants.h" 27 #include "chrome/installer/util/installation_state.h" 28 #include "chrome/installer/util/installer_state.h" 29 #include "chrome/installer/util/util_constants.h" 30 #include "testing/gtest/include/gtest/gtest.h" 31 32 namespace { 33 34 class SetupUtilTestWithDir : public testing::Test { 35 protected: 36 virtual void SetUp() OVERRIDE { 37 // Create a temp directory for testing. 38 ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); 39 } 40 41 virtual void TearDown() OVERRIDE { 42 // Clean up test directory manually so we can fail if it leaks. 43 ASSERT_TRUE(test_dir_.Delete()); 44 } 45 46 // The temporary directory used to contain the test operations. 47 base::ScopedTempDir test_dir_; 48 }; 49 50 // The privilege tested in ScopeTokenPrivilege tests below. 51 // Use SE_RESTORE_NAME as it is one of the many privileges that is available, 52 // but not enabled by default on processes running at high integrity. 53 static const wchar_t kTestedPrivilege[] = SE_RESTORE_NAME; 54 55 // Returns true if the current process' token has privilege |privilege_name| 56 // enabled. 57 bool CurrentProcessHasPrivilege(const wchar_t* privilege_name) { 58 HANDLE temp_handle; 59 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, 60 &temp_handle)) { 61 ADD_FAILURE(); 62 return false; 63 } 64 65 base::win::ScopedHandle token(temp_handle); 66 67 // First get the size of the buffer needed for |privileges| below. 68 DWORD size; 69 EXPECT_FALSE(::GetTokenInformation(token.Get(), TokenPrivileges, NULL, 0, 70 &size)); 71 72 scoped_ptr<BYTE[]> privileges_bytes(new BYTE[size]); 73 TOKEN_PRIVILEGES* privileges = 74 reinterpret_cast<TOKEN_PRIVILEGES*>(privileges_bytes.get()); 75 76 if (!::GetTokenInformation(token.Get(), TokenPrivileges, privileges, size, 77 &size)) { 78 ADD_FAILURE(); 79 return false; 80 } 81 82 // There is no point getting a buffer to store more than |privilege_name|\0 as 83 // anything longer will obviously not be equal to |privilege_name|. 84 const DWORD desired_size = wcslen(privilege_name); 85 const DWORD buffer_size = desired_size + 1; 86 scoped_ptr<wchar_t[]> name_buffer(new wchar_t[buffer_size]); 87 for (int i = privileges->PrivilegeCount - 1; i >= 0 ; --i) { 88 LUID_AND_ATTRIBUTES& luid_and_att = privileges->Privileges[i]; 89 DWORD size = buffer_size; 90 ::LookupPrivilegeName(NULL, &luid_and_att.Luid, name_buffer.get(), &size); 91 if (size == desired_size && 92 wcscmp(name_buffer.get(), privilege_name) == 0) { 93 return luid_and_att.Attributes == SE_PRIVILEGE_ENABLED; 94 } 95 } 96 return false; 97 } 98 99 } // namespace 100 101 // Test that we are parsing Chrome version correctly. 102 TEST_F(SetupUtilTestWithDir, GetMaxVersionFromArchiveDirTest) { 103 // Create a version dir 104 base::FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0"); 105 base::CreateDirectory(chrome_dir); 106 ASSERT_TRUE(base::PathExists(chrome_dir)); 107 scoped_ptr<Version> version( 108 installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 109 ASSERT_EQ(version->GetString(), "1.0.0.0"); 110 111 base::DeleteFile(chrome_dir, true); 112 ASSERT_FALSE(base::PathExists(chrome_dir)); 113 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); 114 115 chrome_dir = test_dir_.path().AppendASCII("ABC"); 116 base::CreateDirectory(chrome_dir); 117 ASSERT_TRUE(base::PathExists(chrome_dir)); 118 ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL); 119 120 chrome_dir = test_dir_.path().AppendASCII("2.3.4.5"); 121 base::CreateDirectory(chrome_dir); 122 ASSERT_TRUE(base::PathExists(chrome_dir)); 123 version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 124 ASSERT_EQ(version->GetString(), "2.3.4.5"); 125 126 // Create multiple version dirs, ensure that we select the greatest. 127 chrome_dir = test_dir_.path().AppendASCII("9.9.9.9"); 128 base::CreateDirectory(chrome_dir); 129 ASSERT_TRUE(base::PathExists(chrome_dir)); 130 chrome_dir = test_dir_.path().AppendASCII("1.1.1.1"); 131 base::CreateDirectory(chrome_dir); 132 ASSERT_TRUE(base::PathExists(chrome_dir)); 133 134 version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path())); 135 ASSERT_EQ(version->GetString(), "9.9.9.9"); 136 } 137 138 TEST_F(SetupUtilTestWithDir, DeleteFileFromTempProcess) { 139 base::FilePath test_file; 140 base::CreateTemporaryFileInDir(test_dir_.path(), &test_file); 141 ASSERT_TRUE(base::PathExists(test_file)); 142 base::WriteFile(test_file, "foo", 3); 143 EXPECT_TRUE(installer::DeleteFileFromTempProcess(test_file, 0)); 144 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); 145 EXPECT_FALSE(base::PathExists(test_file)); 146 } 147 148 // Note: This test is only valid when run at high integrity (i.e. it will fail 149 // at medium integrity). 150 TEST(SetupUtilTest, ScopedTokenPrivilegeBasic) { 151 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 152 153 { 154 installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); 155 ASSERT_TRUE(test_scoped_privilege.is_enabled()); 156 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 157 } 158 159 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 160 } 161 162 // Note: This test is only valid when run at high integrity (i.e. it will fail 163 // at medium integrity). 164 TEST(SetupUtilTest, ScopedTokenPrivilegeAlreadyEnabled) { 165 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 166 167 { 168 installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege); 169 ASSERT_TRUE(test_scoped_privilege.is_enabled()); 170 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 171 { 172 installer::ScopedTokenPrivilege dup_scoped_privilege(kTestedPrivilege); 173 ASSERT_TRUE(dup_scoped_privilege.is_enabled()); 174 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 175 } 176 ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege)); 177 } 178 179 ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege)); 180 } 181 182 const char kAdjustProcessPriority[] = "adjust-process-priority"; 183 184 PriorityClassChangeResult DoProcessPriorityAdjustment() { 185 return installer::AdjustProcessPriority() ? PCCR_CHANGED : PCCR_UNCHANGED; 186 } 187 188 namespace { 189 190 // A scoper that sets/resets the current process's priority class. 191 class ScopedPriorityClass { 192 public: 193 // Applies |priority_class|, returning an instance if a change was made. 194 // Otherwise, returns an empty scoped_ptr. 195 static scoped_ptr<ScopedPriorityClass> Create(DWORD priority_class); 196 ~ScopedPriorityClass(); 197 198 private: 199 explicit ScopedPriorityClass(DWORD original_priority_class); 200 DWORD original_priority_class_; 201 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityClass); 202 }; 203 204 scoped_ptr<ScopedPriorityClass> ScopedPriorityClass::Create( 205 DWORD priority_class) { 206 HANDLE this_process = ::GetCurrentProcess(); 207 DWORD original_priority_class = ::GetPriorityClass(this_process); 208 EXPECT_NE(0U, original_priority_class); 209 if (original_priority_class && original_priority_class != priority_class) { 210 BOOL result = ::SetPriorityClass(this_process, priority_class); 211 EXPECT_NE(FALSE, result); 212 if (result) { 213 return scoped_ptr<ScopedPriorityClass>( 214 new ScopedPriorityClass(original_priority_class)); 215 } 216 } 217 return scoped_ptr<ScopedPriorityClass>(); 218 } 219 220 ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class) 221 : original_priority_class_(original_priority_class) {} 222 223 ScopedPriorityClass::~ScopedPriorityClass() { 224 BOOL result = ::SetPriorityClass(::GetCurrentProcess(), 225 original_priority_class_); 226 EXPECT_NE(FALSE, result); 227 } 228 229 PriorityClassChangeResult RelaunchAndDoProcessPriorityAdjustment() { 230 CommandLine cmd_line(*CommandLine::ForCurrentProcess()); 231 cmd_line.AppendSwitch(kAdjustProcessPriority); 232 base::ProcessHandle process_handle = NULL; 233 int exit_code = 0; 234 if (!base::LaunchProcess(cmd_line, base::LaunchOptions(), 235 &process_handle)) { 236 ADD_FAILURE() << " to launch subprocess."; 237 } else if (!base::WaitForExitCode(process_handle, &exit_code)) { 238 ADD_FAILURE() << " to wait for subprocess to exit."; 239 } else { 240 return static_cast<PriorityClassChangeResult>(exit_code); 241 } 242 return PCCR_UNKNOWN; 243 } 244 245 } // namespace 246 247 // Launching a subprocess at normal priority class is a noop. 248 TEST(SetupUtilTest, AdjustFromNormalPriority) { 249 ASSERT_EQ(NORMAL_PRIORITY_CLASS, ::GetPriorityClass(::GetCurrentProcess())); 250 EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); 251 } 252 253 // Launching a subprocess below normal priority class drops it to bg mode for 254 // sufficiently recent operating systems. 255 TEST(SetupUtilTest, AdjustFromBelowNormalPriority) { 256 scoped_ptr<ScopedPriorityClass> below_normal = 257 ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS); 258 ASSERT_TRUE(below_normal); 259 if (base::win::GetVersion() > base::win::VERSION_SERVER_2003) 260 EXPECT_EQ(PCCR_CHANGED, RelaunchAndDoProcessPriorityAdjustment()); 261 else 262 EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); 263 } 264 265 namespace { 266 267 // A test fixture that configures an InstallationState and an InstallerState 268 // with a product being updated. 269 class FindArchiveToPatchTest : public SetupUtilTestWithDir { 270 protected: 271 class FakeInstallationState : public installer::InstallationState { 272 }; 273 274 class FakeProductState : public installer::ProductState { 275 public: 276 static FakeProductState* FromProductState(const ProductState* product) { 277 return static_cast<FakeProductState*>(const_cast<ProductState*>(product)); 278 } 279 280 void set_version(const Version& version) { 281 if (version.IsValid()) 282 version_.reset(new Version(version)); 283 else 284 version_.reset(); 285 } 286 287 void set_uninstall_command(const CommandLine& uninstall_command) { 288 uninstall_command_ = uninstall_command; 289 } 290 }; 291 292 virtual void SetUp() OVERRIDE { 293 SetupUtilTestWithDir::SetUp(); 294 product_version_ = Version("30.0.1559.0"); 295 max_version_ = Version("47.0.1559.0"); 296 297 // Install the product according to the version. 298 original_state_.reset(new FakeInstallationState()); 299 InstallProduct(); 300 301 // Prepare to update the product in the temp dir. 302 installer_state_.reset(new installer::InstallerState( 303 kSystemInstall_ ? installer::InstallerState::SYSTEM_LEVEL : 304 installer::InstallerState::USER_LEVEL)); 305 installer_state_->AddProductFromState( 306 kProductType_, 307 *original_state_->GetProductState(kSystemInstall_, kProductType_)); 308 309 // Create archives in the two version dirs. 310 ASSERT_TRUE( 311 base::CreateDirectory(GetProductVersionArchivePath().DirName())); 312 ASSERT_EQ(1, base::WriteFile(GetProductVersionArchivePath(), "a", 1)); 313 ASSERT_TRUE( 314 base::CreateDirectory(GetMaxVersionArchivePath().DirName())); 315 ASSERT_EQ(1, base::WriteFile(GetMaxVersionArchivePath(), "b", 1)); 316 } 317 318 virtual void TearDown() OVERRIDE { 319 original_state_.reset(); 320 SetupUtilTestWithDir::TearDown(); 321 } 322 323 base::FilePath GetArchivePath(const Version& version) const { 324 return test_dir_.path() 325 .AppendASCII(version.GetString()) 326 .Append(installer::kInstallerDir) 327 .Append(installer::kChromeArchive); 328 } 329 330 base::FilePath GetMaxVersionArchivePath() const { 331 return GetArchivePath(max_version_); 332 } 333 334 base::FilePath GetProductVersionArchivePath() const { 335 return GetArchivePath(product_version_); 336 } 337 338 void InstallProduct() { 339 FakeProductState* product = FakeProductState::FromProductState( 340 original_state_->GetNonVersionedProductState(kSystemInstall_, 341 kProductType_)); 342 343 product->set_version(product_version_); 344 CommandLine uninstall_command( 345 test_dir_.path().AppendASCII(product_version_.GetString()) 346 .Append(installer::kInstallerDir) 347 .Append(installer::kSetupExe)); 348 uninstall_command.AppendSwitch(installer::switches::kUninstall); 349 product->set_uninstall_command(uninstall_command); 350 } 351 352 void UninstallProduct() { 353 FakeProductState::FromProductState( 354 original_state_->GetNonVersionedProductState(kSystemInstall_, 355 kProductType_)) 356 ->set_version(Version()); 357 } 358 359 static const bool kSystemInstall_; 360 static const BrowserDistribution::Type kProductType_; 361 Version product_version_; 362 Version max_version_; 363 scoped_ptr<FakeInstallationState> original_state_; 364 scoped_ptr<installer::InstallerState> installer_state_; 365 }; 366 367 const bool FindArchiveToPatchTest::kSystemInstall_ = false; 368 const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ = 369 BrowserDistribution::CHROME_BROWSER; 370 371 } // namespace 372 373 // Test that the path to the advertised product version is found. 374 TEST_F(FindArchiveToPatchTest, ProductVersionFound) { 375 base::FilePath patch_source(installer::FindArchiveToPatch( 376 *original_state_, *installer_state_)); 377 EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value()); 378 } 379 380 // Test that the path to the max version is found if the advertised version is 381 // missing. 382 TEST_F(FindArchiveToPatchTest, MaxVersionFound) { 383 // The patch file is absent. 384 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); 385 base::FilePath patch_source(installer::FindArchiveToPatch( 386 *original_state_, *installer_state_)); 387 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); 388 389 // The product doesn't appear to be installed, so the max version is found. 390 UninstallProduct(); 391 patch_source = installer::FindArchiveToPatch( 392 *original_state_, *installer_state_); 393 EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); 394 } 395 396 // Test that an empty path is returned if no version is found. 397 TEST_F(FindArchiveToPatchTest, NoVersionFound) { 398 // The product doesn't appear to be installed and no archives are present. 399 UninstallProduct(); 400 ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); 401 ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false)); 402 403 base::FilePath patch_source(installer::FindArchiveToPatch( 404 *original_state_, *installer_state_)); 405 EXPECT_EQ(base::FilePath::StringType(), patch_source.value()); 406 } 407 408 namespace { 409 410 class MigrateMultiToSingleTest : public testing::Test { 411 protected: 412 virtual void SetUp() OVERRIDE { 413 registry_override_manager_.OverrideRegistry(kRootKey); 414 } 415 416 static const bool kSystemLevel = false; 417 static const HKEY kRootKey; 418 static const wchar_t kVersionString[]; 419 static const wchar_t kMultiChannel[]; 420 registry_util::RegistryOverrideManager registry_override_manager_; 421 }; 422 423 const bool MigrateMultiToSingleTest::kSystemLevel; 424 const HKEY MigrateMultiToSingleTest::kRootKey = 425 kSystemLevel ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 426 const wchar_t MigrateMultiToSingleTest::kVersionString[] = L"30.0.1574.0"; 427 const wchar_t MigrateMultiToSingleTest::kMultiChannel[] = 428 L"2.0-dev-multi-chromeframe"; 429 430 } // namespace 431 432 // Test migrating Chrome Frame from multi to single. 433 TEST_F(MigrateMultiToSingleTest, ChromeFrame) { 434 installer::ProductState chrome_frame; 435 installer::ProductState binaries; 436 DWORD usagestats = 0; 437 438 // Set up a config with dev-channel multi-install GCF. 439 base::win::RegKey key; 440 441 BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( 442 BrowserDistribution::CHROME_BINARIES); 443 ASSERT_EQ(ERROR_SUCCESS, 444 base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), 445 KEY_SET_VALUE) 446 .WriteValue(google_update::kRegVersionField, kVersionString)); 447 ASSERT_EQ(ERROR_SUCCESS, 448 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 449 KEY_SET_VALUE) 450 .WriteValue(google_update::kRegApField, kMultiChannel)); 451 ASSERT_EQ(ERROR_SUCCESS, 452 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 453 KEY_SET_VALUE) 454 .WriteValue(google_update::kRegUsageStatsField, 1U)); 455 456 dist = BrowserDistribution::GetSpecificDistribution( 457 BrowserDistribution::CHROME_FRAME); 458 ASSERT_EQ(ERROR_SUCCESS, 459 base::win::RegKey(kRootKey, dist->GetVersionKey().c_str(), 460 KEY_SET_VALUE) 461 .WriteValue(google_update::kRegVersionField, kVersionString)); 462 ASSERT_EQ(ERROR_SUCCESS, 463 base::win::RegKey(kRootKey, dist->GetStateKey().c_str(), 464 KEY_SET_VALUE) 465 .WriteValue(google_update::kRegApField, kMultiChannel)); 466 467 // Do the registry migration. 468 installer::InstallationState machine_state; 469 machine_state.Initialize(); 470 471 installer::MigrateGoogleUpdateStateMultiToSingle( 472 kSystemLevel, 473 BrowserDistribution::CHROME_FRAME, 474 machine_state); 475 476 // Confirm that usagestats were copied to CF and that its channel was 477 // stripped. 478 ASSERT_TRUE(chrome_frame.Initialize(kSystemLevel, 479 BrowserDistribution::CHROME_FRAME)); 480 EXPECT_TRUE(chrome_frame.GetUsageStats(&usagestats)); 481 EXPECT_EQ(1U, usagestats); 482 EXPECT_EQ(L"2.0-dev", chrome_frame.channel().value()); 483 484 // Confirm that the binaries' channel no longer contains GCF. 485 ASSERT_TRUE(binaries.Initialize(kSystemLevel, 486 BrowserDistribution::CHROME_BINARIES)); 487 EXPECT_EQ(L"2.0-dev-multi", binaries.channel().value()); 488 } 489 490 TEST(SetupUtilTest, ContainsUnsupportedSwitch) { 491 EXPECT_FALSE(installer::ContainsUnsupportedSwitch( 492 CommandLine::FromString(L"foo.exe"))); 493 EXPECT_FALSE(installer::ContainsUnsupportedSwitch( 494 CommandLine::FromString(L"foo.exe --multi-install --chrome"))); 495 EXPECT_TRUE(installer::ContainsUnsupportedSwitch( 496 CommandLine::FromString(L"foo.exe --chrome-frame"))); 497 } 498