setup_util_unittest.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
1f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// found in the LICENSE file.
4f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
5f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/setup/setup_util_unittest.h"
6f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
7f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include <windows.h>
8f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
9f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include <string>
10f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/command_line.h"
121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/file_util.h"
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/files/scoped_temp_dir.h"
14f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/memory/scoped_ptr.h"
15f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/process_util.h"
161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/threading/platform_thread.h"
17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/time/time.h"
18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/version.h"
19f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/win/scoped_handle.h"
20f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/win/windows_version.h"
21f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/setup/setup_util.h"
22f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/setup/setup_constants.h"
23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/util/installation_state.h"
24f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/util/installer_state.h"
25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/installer/util/util_constants.h"
26f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace {
29f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class SetupUtilTestWithDir : public testing::Test {
31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) protected:
32f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  virtual void SetUp() OVERRIDE {
33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    // Create a temp directory for testing.
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  virtual void TearDown() OVERRIDE {
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    // Clean up test directory manually so we can fail if it leaks.
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ASSERT_TRUE(test_dir_.Delete());
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // The temporary directory used to contain the test operations.
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  base::ScopedTempDir test_dir_;
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// The privilege tested in ScopeTokenPrivilege tests below.
47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Use SE_RESTORE_NAME as it is one of the many privileges that is available,
48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// but not enabled by default on processes running at high integrity.
49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)static const wchar_t kTestedPrivilege[] = SE_RESTORE_NAME;
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
51f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Returns true if the current process' token has privilege |privilege_name|
52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// enabled.
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)bool CurrentProcessHasPrivilege(const wchar_t* privilege_name) {
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  base::win::ScopedHandle token;
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                          token.Receive())) {
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    ADD_FAILURE();
58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return false;
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // First get the size of the buffer needed for |privileges| below.
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  DWORD size;
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  EXPECT_FALSE(::GetTokenInformation(token, TokenPrivileges, NULL, 0, &size));
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  scoped_ptr<BYTE[]> privileges_bytes(new BYTE[size]);
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  TOKEN_PRIVILEGES* privileges =
67f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      reinterpret_cast<TOKEN_PRIVILEGES*>(privileges_bytes.get());
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
69f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!::GetTokenInformation(token, TokenPrivileges, privileges, size, &size)) {
70f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    ADD_FAILURE();
71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return false;
72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // There is no point getting a buffer to store more than |privilege_name|\0 as
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // anything longer will obviously not be equal to |privilege_name|.
76f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  const DWORD desired_size = wcslen(privilege_name);
77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  const DWORD buffer_size = desired_size + 1;
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  scoped_ptr<wchar_t[]> name_buffer(new wchar_t[buffer_size]);
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  for (int i = privileges->PrivilegeCount - 1; i >= 0 ; --i) {
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    LUID_AND_ATTRIBUTES& luid_and_att = privileges->Privileges[i];
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    DWORD size = buffer_size;
82f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    ::LookupPrivilegeName(NULL, &luid_and_att.Luid, name_buffer.get(), &size);
83f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if (size == desired_size &&
84f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        wcscmp(name_buffer.get(), privilege_name) == 0) {
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return luid_and_att.Attributes == SE_PRIVILEGE_ENABLED;
86f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    }
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
88  return false;
89}
90
91}  // namespace
92
93// Test that we are parsing Chrome version correctly.
94TEST_F(SetupUtilTestWithDir, GetMaxVersionFromArchiveDirTest) {
95  // Create a version dir
96  base::FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0");
97  file_util::CreateDirectory(chrome_dir);
98  ASSERT_TRUE(base::PathExists(chrome_dir));
99  scoped_ptr<Version> version(
100      installer::GetMaxVersionFromArchiveDir(test_dir_.path()));
101  ASSERT_EQ(version->GetString(), "1.0.0.0");
102
103  base::DeleteFile(chrome_dir, true);
104  ASSERT_FALSE(base::PathExists(chrome_dir));
105  ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL);
106
107  chrome_dir = test_dir_.path().AppendASCII("ABC");
108  file_util::CreateDirectory(chrome_dir);
109  ASSERT_TRUE(base::PathExists(chrome_dir));
110  ASSERT_TRUE(installer::GetMaxVersionFromArchiveDir(test_dir_.path()) == NULL);
111
112  chrome_dir = test_dir_.path().AppendASCII("2.3.4.5");
113  file_util::CreateDirectory(chrome_dir);
114  ASSERT_TRUE(base::PathExists(chrome_dir));
115  version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path()));
116  ASSERT_EQ(version->GetString(), "2.3.4.5");
117
118  // Create multiple version dirs, ensure that we select the greatest.
119  chrome_dir = test_dir_.path().AppendASCII("9.9.9.9");
120  file_util::CreateDirectory(chrome_dir);
121  ASSERT_TRUE(base::PathExists(chrome_dir));
122  chrome_dir = test_dir_.path().AppendASCII("1.1.1.1");
123  file_util::CreateDirectory(chrome_dir);
124  ASSERT_TRUE(base::PathExists(chrome_dir));
125
126  version.reset(installer::GetMaxVersionFromArchiveDir(test_dir_.path()));
127  ASSERT_EQ(version->GetString(), "9.9.9.9");
128}
129
130TEST_F(SetupUtilTestWithDir, DeleteFileFromTempProcess) {
131  base::FilePath test_file;
132  file_util::CreateTemporaryFileInDir(test_dir_.path(), &test_file);
133  ASSERT_TRUE(base::PathExists(test_file));
134  file_util::WriteFile(test_file, "foo", 3);
135  EXPECT_TRUE(installer::DeleteFileFromTempProcess(test_file, 0));
136  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
137  EXPECT_FALSE(base::PathExists(test_file));
138}
139
140// Note: This test is only valid when run at high integrity (i.e. it will fail
141// at medium integrity).
142TEST(SetupUtilTest, ScopedTokenPrivilegeBasic) {
143  ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege));
144
145  {
146    installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege);
147    ASSERT_TRUE(test_scoped_privilege.is_enabled());
148    ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege));
149  }
150
151  ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege));
152}
153
154// Note: This test is only valid when run at high integrity (i.e. it will fail
155// at medium integrity).
156TEST(SetupUtilTest, ScopedTokenPrivilegeAlreadyEnabled) {
157  ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege));
158
159  {
160    installer::ScopedTokenPrivilege test_scoped_privilege(kTestedPrivilege);
161    ASSERT_TRUE(test_scoped_privilege.is_enabled());
162    ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege));
163    {
164      installer::ScopedTokenPrivilege dup_scoped_privilege(kTestedPrivilege);
165      ASSERT_TRUE(dup_scoped_privilege.is_enabled());
166      ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege));
167    }
168    ASSERT_TRUE(CurrentProcessHasPrivilege(kTestedPrivilege));
169  }
170
171  ASSERT_FALSE(CurrentProcessHasPrivilege(kTestedPrivilege));
172}
173
174const char kAdjustProcessPriority[] = "adjust-process-priority";
175
176PriorityClassChangeResult DoProcessPriorityAdjustment() {
177  return installer::AdjustProcessPriority() ? PCCR_CHANGED : PCCR_UNCHANGED;
178}
179
180namespace {
181
182// A scoper that sets/resets the current process's priority class.
183class ScopedPriorityClass {
184 public:
185  // Applies |priority_class|, returning an instance if a change was made.
186  // Otherwise, returns an empty scoped_ptr.
187  static scoped_ptr<ScopedPriorityClass> Create(DWORD priority_class);
188  ~ScopedPriorityClass();
189
190 private:
191  explicit ScopedPriorityClass(DWORD original_priority_class);
192  DWORD original_priority_class_;
193  DISALLOW_COPY_AND_ASSIGN(ScopedPriorityClass);
194};
195
196scoped_ptr<ScopedPriorityClass> ScopedPriorityClass::Create(
197    DWORD priority_class) {
198  HANDLE this_process = ::GetCurrentProcess();
199  DWORD original_priority_class = ::GetPriorityClass(this_process);
200  EXPECT_NE(0U, original_priority_class);
201  if (original_priority_class && original_priority_class != priority_class) {
202    BOOL result = ::SetPriorityClass(this_process, priority_class);
203    EXPECT_NE(FALSE, result);
204    if (result) {
205      return scoped_ptr<ScopedPriorityClass>(
206          new ScopedPriorityClass(original_priority_class));
207    }
208  }
209  return scoped_ptr<ScopedPriorityClass>();
210}
211
212ScopedPriorityClass::ScopedPriorityClass(DWORD original_priority_class)
213    : original_priority_class_(original_priority_class) {}
214
215ScopedPriorityClass::~ScopedPriorityClass() {
216  BOOL result = ::SetPriorityClass(::GetCurrentProcess(),
217                                   original_priority_class_);
218  EXPECT_NE(FALSE, result);
219}
220
221PriorityClassChangeResult RelaunchAndDoProcessPriorityAdjustment() {
222  CommandLine cmd_line(*CommandLine::ForCurrentProcess());
223  cmd_line.AppendSwitch(kAdjustProcessPriority);
224  base::ProcessHandle process_handle = NULL;
225  int exit_code = 0;
226  if (!base::LaunchProcess(cmd_line, base::LaunchOptions(),
227                           &process_handle)) {
228    ADD_FAILURE() << " to launch subprocess.";
229  } else if (!base::WaitForExitCode(process_handle, &exit_code)) {
230    ADD_FAILURE() << " to wait for subprocess to exit.";
231  } else {
232    return static_cast<PriorityClassChangeResult>(exit_code);
233  }
234  return PCCR_UNKNOWN;
235}
236
237}  // namespace
238
239// Launching a subprocess at normal priority class is a noop.
240TEST(SetupUtilTest, AdjustFromNormalPriority) {
241  ASSERT_EQ(NORMAL_PRIORITY_CLASS, ::GetPriorityClass(::GetCurrentProcess()));
242  EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment());
243}
244
245// Launching a subprocess below normal priority class drops it to bg mode for
246// sufficiently recent operating systems.
247TEST(SetupUtilTest, AdjustFromBelowNormalPriority) {
248  scoped_ptr<ScopedPriorityClass> below_normal =
249      ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS);
250  ASSERT_TRUE(below_normal);
251  if (base::win::GetVersion() > base::win::VERSION_SERVER_2003)
252    EXPECT_EQ(PCCR_CHANGED, RelaunchAndDoProcessPriorityAdjustment());
253  else
254    EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment());
255}
256
257namespace {
258
259// A test fixture that configures an InstallationState and an InstallerState
260// with a product being updated.
261class FindArchiveToPatchTest : public SetupUtilTestWithDir {
262 protected:
263  class FakeInstallationState : public installer::InstallationState {
264  };
265
266  class FakeProductState : public installer::ProductState {
267   public:
268    static FakeProductState* FromProductState(const ProductState* product) {
269      return static_cast<FakeProductState*>(const_cast<ProductState*>(product));
270    }
271
272    void set_version(const Version& version) {
273      if (version.IsValid())
274        version_.reset(new Version(version));
275      else
276        version_.reset();
277    }
278
279    void set_uninstall_command(const CommandLine& uninstall_command) {
280      uninstall_command_ = uninstall_command;
281    }
282  };
283
284  virtual void SetUp() OVERRIDE {
285    SetupUtilTestWithDir::SetUp();
286    product_version_ = Version("30.0.1559.0");
287    max_version_ = Version("47.0.1559.0");
288
289    // Install the product according to the version.
290    original_state_.reset(new FakeInstallationState());
291    InstallProduct();
292
293    // Prepare to update the product in the temp dir.
294    installer_state_.reset(new installer::InstallerState(
295        kSystemInstall_ ? installer::InstallerState::SYSTEM_LEVEL :
296        installer::InstallerState::USER_LEVEL));
297    installer_state_->AddProductFromState(
298        kProductType_,
299        *original_state_->GetProductState(kSystemInstall_, kProductType_));
300
301    // Create archives in the two version dirs.
302    ASSERT_TRUE(
303        file_util::CreateDirectory(GetProductVersionArchivePath().DirName()));
304    ASSERT_EQ(1, file_util::WriteFile(GetProductVersionArchivePath(), "a", 1));
305    ASSERT_TRUE(
306        file_util::CreateDirectory(GetMaxVersionArchivePath().DirName()));
307    ASSERT_EQ(1, file_util::WriteFile(GetMaxVersionArchivePath(), "b", 1));
308  }
309
310  virtual void TearDown() OVERRIDE {
311    original_state_.reset();
312    SetupUtilTestWithDir::TearDown();
313  }
314
315  base::FilePath GetArchivePath(const Version& version) const {
316    return test_dir_.path()
317        .AppendASCII(version.GetString())
318        .Append(installer::kInstallerDir)
319        .Append(installer::kChromeArchive);
320  }
321
322  base::FilePath GetMaxVersionArchivePath() const {
323    return GetArchivePath(max_version_);
324  }
325
326  base::FilePath GetProductVersionArchivePath() const {
327    return GetArchivePath(product_version_);
328  }
329
330  void InstallProduct() {
331    FakeProductState* product = FakeProductState::FromProductState(
332        original_state_->GetNonVersionedProductState(kSystemInstall_,
333                                                     kProductType_));
334
335    product->set_version(product_version_);
336    CommandLine uninstall_command(
337        test_dir_.path().AppendASCII(product_version_.GetString())
338        .Append(installer::kInstallerDir)
339        .Append(installer::kSetupExe));
340    uninstall_command.AppendSwitch(installer::switches::kUninstall);
341    product->set_uninstall_command(uninstall_command);
342  }
343
344  void UninstallProduct() {
345    FakeProductState::FromProductState(
346        original_state_->GetNonVersionedProductState(kSystemInstall_,
347                                                     kProductType_))
348        ->set_version(Version());
349  }
350
351  static const bool kSystemInstall_;
352  static const BrowserDistribution::Type kProductType_;
353  Version product_version_;
354  Version max_version_;
355  scoped_ptr<FakeInstallationState> original_state_;
356  scoped_ptr<installer::InstallerState> installer_state_;
357};
358
359const bool FindArchiveToPatchTest::kSystemInstall_ = false;
360const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ =
361    BrowserDistribution::CHROME_BROWSER;
362
363}  // namespace
364
365// Test that the path to the advertised product version is found.
366TEST_F(FindArchiveToPatchTest, ProductVersionFound) {
367  base::FilePath patch_source(installer::FindArchiveToPatch(
368      *original_state_, *installer_state_));
369  EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value());
370}
371
372// Test that the path to the max version is found if the advertised version is
373// missing.
374TEST_F(FindArchiveToPatchTest, MaxVersionFound) {
375  // The patch file is absent.
376  ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false));
377  base::FilePath patch_source(installer::FindArchiveToPatch(
378      *original_state_, *installer_state_));
379  EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value());
380
381  // The product doesn't appear to be installed, so the max version is found.
382  UninstallProduct();
383  patch_source = installer::FindArchiveToPatch(
384      *original_state_, *installer_state_);
385  EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value());
386}
387
388// Test that an empty path is returned if no version is found.
389TEST_F(FindArchiveToPatchTest, NoVersionFound) {
390  // The product doesn't appear to be installed and no archives are present.
391  UninstallProduct();
392  ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false));
393  ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false));
394
395  base::FilePath patch_source(installer::FindArchiveToPatch(
396      *original_state_, *installer_state_));
397  EXPECT_EQ(base::FilePath::StringType(), patch_source.value());
398}
399