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