910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/first_run/upgrade_util_win.h"

#include <objbase.h>

#include <windows.h>

#include <psapi.h>
#include <shellapi.h>
#include <wrl/client.h>

#include <algorithm>
#include <ios>
#include <string>

#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"
#include "base/win/windows_version.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_process_singleton.h"
#include "chrome/browser/first_run/upgrade_util.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/win/browser_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/app_command.h"
#include "chrome/installer/util/per_install_values.h"
#include "chrome/installer/util/util_constants.h"
#include "components/prefs/pref_service.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/ui_base_switches.h"

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#endif

namespace {

bool GetNewerChromeFile(base::FilePath* path) {
  if (!base::PathService::Get(base::DIR_EXE, path))
    return false;
  *path = path->Append(installer::kChromeNewExe);
  return true;
}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Holds the result of the IPC to CoCreate `GoogleUpdate3Web`.
struct CreateGoogleUpdate3WebResult
    : public base::RefCountedThreadSafe<CreateGoogleUpdate3WebResult> {
  Microsoft::WRL::ComPtr<IStream> stream;
  base::WaitableEvent completion_event;

 private:
  friend class base::RefCountedThreadSafe<CreateGoogleUpdate3WebResult>;
  ~CreateGoogleUpdate3WebResult() = default;
};

// CoCreates the `GoogleUpdate3Web` class, and if successful, marshals the
// resulting interface into `result->stream`. Signals `result->completion_event`
// on successful or failed completion.
void CreateAndMarshalGoogleUpdate3Web(
    scoped_refptr<CreateGoogleUpdate3WebResult> result) {
  const absl::Cleanup signal_completion_event = [&result] {
    result->completion_event.Signal();
  };

  Microsoft::WRL::ComPtr<IUnknown> unknown;
  {
    TRACE_EVENT0("startup", "InvokeGoogleUpdateForRename CoCreateInstance");
    const HRESULT hr =
        ::CoCreateInstance(__uuidof(GoogleUpdate3WebSystemClass), nullptr,
                           CLSCTX_ALL, IID_PPV_ARGS(&unknown));
    if (FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename CoCreateInstance failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "CoCreate GoogleUpdate3WebSystemClass failed; hr = "
                 << std::hex << hr;
      return;
    }
  }
  const HRESULT hr = ::CoMarshalInterThreadInterfaceInStream(
      __uuidof(IUnknown), unknown.Get(), &result->stream);
  if (FAILED(hr)) {
    TRACE_EVENT_INSTANT1("startup",
                         "InvokeGoogleUpdateForRename "
                         "CoMarshalInterThreadInterfaceInStream failed",
                         TRACE_EVENT_SCOPE_THREAD, "hr", hr);
    LOG(ERROR) << "CoMarshalInterThreadInterfaceInStream "
                  "GoogleUpdate3WebSystemClass failed; hr = "
               << std::hex << hr;
  }
}

// CoCreates the Google Update `GoogleUpdate3WebSystemClass` in a `ThreadPool`
// thread with a timeout, if the `ThreadPool` is operational. The starting value
// for the timeout is 15 seconds. If the CoCreate times out, the timeout is
// increased by 15 seconds at each failed attempt and persisted for the next
// attempt.
//
// If the `ThreadPool` is not operational, the CoCreate is done
// without a timeout.
Microsoft::WRL::ComPtr<IUnknown> CreateGoogleUpdate3Web() {
  constexpr int kDefaultTimeoutIncrementSeconds = 15;
  constexpr base::TimeDelta kMaxTimeAfterSystemStartup = base::Seconds(150);

  auto result = base::MakeRefCounted<CreateGoogleUpdate3WebResult>();
  if (base::ThreadPool::CreateCOMSTATaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_BLOCKING})
          ->PostTask(
              FROM_HERE,
              base::BindOnce(&CreateAndMarshalGoogleUpdate3Web, result))) {
    installer::PerInstallValue creation_timeout(
        L"ProcessLauncherCreationTimeout");
    const base::TimeDelta timeout = base::Seconds(
        creation_timeout.Get()
            .value_or(base::Value(kDefaultTimeoutIncrementSeconds))
            .GetIfInt()
            .value_or(kDefaultTimeoutIncrementSeconds));
    const base::ElapsedTimer timer;
    const bool is_at_startup =
        base::SysInfo::Uptime() <= kMaxTimeAfterSystemStartup;
    if (!result->completion_event.TimedWait(timeout)) {
      base::UmaHistogramMediumTimes(
          is_at_startup
              ? "Startup.CreateProcessLauncher2.TimedWaitFailedAtStartup"
              : "Startup.CreateProcessLauncher2.TimedWaitFailed",
          timer.Elapsed());
      creation_timeout.Set(base::Value(static_cast<int>(timeout.InSeconds()) +
                                       kDefaultTimeoutIncrementSeconds));
      TRACE_EVENT_INSTANT0(
          "startup", "InvokeGoogleUpdateForRename CoCreateInstance timed out",
          TRACE_EVENT_SCOPE_THREAD);
      LOG(ERROR) << "CoCreate GoogleUpdate3WebSystemClass timed out";
      return {};
    }

    if (!result->stream) {
      return {};
    }
    base::UmaHistogramMediumTimes(
        is_at_startup
            ? "Startup.CreateProcessLauncher2.TimedWaitSucceededAtStartup"
            : "Startup.CreateProcessLauncher2.TimedWaitSucceeded",
        timer.Elapsed());

    Microsoft::WRL::ComPtr<IUnknown> unknown;
    const HRESULT hr =
        ::CoUnmarshalInterface(result->stream.Get(), __uuidof(IUnknown),
                               IID_PPV_ARGS_Helper(&unknown));
    if (FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename CoUnmarshalInterface failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR)
          << "CoUnmarshalInterface GoogleUpdate3WebSystemClass failed; hr = "
          << std::hex << hr;
      return {};
    }

    return unknown;
  }

  // The task could not be posted to the task runner, so CoCreate without a
  // timeout. This could happen in shutdown, where the `ThreadPool` is not
  // operational.
  {
    TRACE_EVENT0("startup", "InvokeGoogleUpdateForRename CoCreateInstance");
    Microsoft::WRL::ComPtr<IUnknown> unknown;
    const HRESULT hr =
        ::CoCreateInstance(__uuidof(GoogleUpdate3WebSystemClass), nullptr,
                           CLSCTX_ALL, IID_PPV_ARGS(&unknown));
    if (FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename CoCreateInstance failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "CoCreate GoogleUpdate3WebSystemClass failed; hr = "
                 << std::hex << hr;
      return {};
    }

    return unknown;
  }
}
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)

bool InvokeGoogleUpdateForRename() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  // This has been identified as very slow on some startups. Detailed trace
  // events below try to shine a light on each steps. crbug.com/1252004
  TRACE_EVENT0("startup", "upgrade_util::InvokeGoogleUpdateForRename");

  Microsoft::WRL::ComPtr<IUnknown> unknown = CreateGoogleUpdate3Web();
  if (!unknown) {
    return false;
  }

  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID,
  // to make sure that marshaling loads the proxy/stub from the correct (HKLM)
  // hive.
  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> update3web;
  if (HRESULT hr = unknown.CopyTo(__uuidof(IGoogleUpdate3WebSystem),
                                  IID_PPV_ARGS_Helper(&update3web));
      FAILED(hr)) {
    hr = unknown.As(&update3web);
    if (FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename QI IGoogleUpdate3Web failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "QI IGoogleUpdate3Web failed; hr = " << std::hex << hr;
      return false;
    }
  }

  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
  {
    Microsoft::WRL::ComPtr<IDispatch> dispatch;
    if (HRESULT hr = update3web->createAppBundleWeb(&dispatch); FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename createAppBundleWeb failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "createAppBundleWeb failed; hr = " << std::hex << hr;
      return false;
    }

    if (HRESULT hr = dispatch.CopyTo(__uuidof(IAppBundleWebSystem),
                                     IID_PPV_ARGS_Helper(&bundle));
        FAILED(hr)) {
      hr = dispatch.As(&bundle);
      if (FAILED(hr)) {
        TRACE_EVENT_INSTANT1(
            "startup", "InvokeGoogleUpdateForRename QI IAppBundleWeb failed",
            TRACE_EVENT_SCOPE_THREAD, "hr", hr);
        LOG(ERROR) << "QI IAppBundleWeb failed; hr = " << std::hex << hr;
        return false;
      }
    }
  }

  if (HRESULT hr = bundle->initialize(); FAILED(hr)) {
    TRACE_EVENT_INSTANT1(
        "startup", "InvokeGoogleUpdateForRename bundle->initialize failed",
        TRACE_EVENT_SCOPE_THREAD, "hr", hr);
    LOG(ERROR) << "bundle->initialize failed; hr = " << std::hex << hr;
    return false;
  }

  if (HRESULT hr = bundle->createInstalledApp(
          base::win::ScopedBstr(install_static::GetAppGuid()).Get());
      FAILED(hr)) {
    TRACE_EVENT_INSTANT1(
        "startup",
        "InvokeGoogleUpdateForRename bundle->createInstalledApp failed",
        TRACE_EVENT_SCOPE_THREAD, "hr", hr);
    LOG(ERROR) << "bundle->createInstalledApp failed; hr = " << std::hex << hr;
    return false;
  }

  Microsoft::WRL::ComPtr<IAppWeb> app;
  {
    Microsoft::WRL::ComPtr<IDispatch> app_dispatch;
    if (HRESULT hr = bundle->get_appWeb(0, &app_dispatch); FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename bundle->get_appWeb failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "bundle->get_appWeb failed; hr = " << std::hex << hr;
      return false;
    }

    if (HRESULT hr = app_dispatch.CopyTo(__uuidof(IAppWebSystem),
                                         IID_PPV_ARGS_Helper(&app));
        FAILED(hr)) {
      hr = app_dispatch.As(&app);
      if (FAILED(hr)) {
        TRACE_EVENT_INSTANT1("startup",
                             "InvokeGoogleUpdateForRename QI IAppWeb failed",
                             TRACE_EVENT_SCOPE_THREAD, "hr", hr);
        LOG(ERROR) << "QI IAppWeb failed; hr = " << std::hex << hr;
        return false;
      }
    }
  }

  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command_web;
  {
    Microsoft::WRL::ComPtr<IDispatch> command_dispatch;
    if (HRESULT hr = app->get_command(
            base::win::ScopedBstr(installer::kCmdRenameChromeExe).Get(),
            &command_dispatch);
        FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename app->get_command failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "app->get_command failed; hr = " << std::hex << hr;
      return false;
    }

    if (HRESULT hr =
            command_dispatch.CopyTo(__uuidof(IAppCommandWebSystem),
                                    IID_PPV_ARGS_Helper(&app_command_web));
        FAILED(hr)) {
      hr = command_dispatch.As(&app_command_web);
      if (FAILED(hr)) {
        TRACE_EVENT_INSTANT1(
            "startup", "InvokeGoogleUpdateForRename QI IAppCommandWeb failed",
            TRACE_EVENT_SCOPE_THREAD, "hr", hr);
        LOG(ERROR) << "QI IAppCommandWeb failed; hr = " << std::hex << hr;
        return false;
      }
    }
  }

  {
    TRACE_EVENT0("startup", "InvokeGoogleUpdateForRename execute");
    if (HRESULT hr =
            app_command_web->execute(base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant,
                                     base::win::ScopedVariant::kEmptyVariant);
        FAILED(hr)) {
      TRACE_EVENT_INSTANT1(
          "startup",
          "InvokeGoogleUpdateForRename app_command_web->execute failed",
          TRACE_EVENT_SCOPE_THREAD, "hr", hr);
      LOG(ERROR) << "app_command_web->execute failed; hr = " << std::hex << hr;
      return false;
    }

    UINT status = 0;
    for (const auto deadline = base::TimeTicks::Now() + base::Seconds(60);
         base::TimeTicks::Now() < deadline;
         base::PlatformThread::Sleep(base::Seconds(1))) {
      if (HRESULT hr = app_command_web->get_status(&status); FAILED(hr)) {
        TRACE_EVENT_INSTANT1(
            "startup",
            "InvokeGoogleUpdateForRename app_command_web->get_status failed",
            TRACE_EVENT_SCOPE_THREAD, "hr", hr);
        LOG(ERROR) << "app_command_web->get_status failed; hr = " << std::hex
                   << hr;
        return false;
      }
      if (status == COMMAND_STATUS_COMPLETE) {
        break;
      }
    }
    if (status != COMMAND_STATUS_COMPLETE) {
      TRACE_EVENT_INSTANT1(
          "startup", "InvokeGoogleUpdateForRename !COMMAND_STATUS_COMPLETE",
          TRACE_EVENT_SCOPE_THREAD, "status", status);
      LOG(ERROR) << "AppCommand timed out with status code " << status;
      return false;
    }
  }

  DWORD exit_code = 0;
  if (HRESULT hr = app_command_web->get_exitCode(&exit_code); FAILED(hr)) {
    TRACE_EVENT_INSTANT1(
        "startup",
        "InvokeGoogleUpdateForRename app_command_web->get_exitCode failed",
        TRACE_EVENT_SCOPE_THREAD, "hr", hr);
    LOG(ERROR) << "app_command_web->get_exitCode failed; hr = " << std::hex
               << hr;
    return false;
  }

  if (exit_code != installer::RENAME_SUCCESSFUL) {
    TRACE_EVENT_INSTANT1("startup",
                         "InvokeGoogleUpdateForRename !RENAME_SUCCESSFUL",
                         TRACE_EVENT_SCOPE_THREAD, "exit_code", exit_code);
    LOG(ERROR) << "Rename process failed with exit code " << exit_code;
    return false;
  }

  TRACE_EVENT_INSTANT0("startup",
                       "InvokeGoogleUpdateForRename RENAME_SUCCESSFUL",
                       TRACE_EVENT_SCOPE_THREAD);

  return true;
#else   // BUILDFLAG(GOOGLE_CHROME_BRANDING)
  return false;
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}

}  // namespace

namespace upgrade_util {

bool RelaunchChromeBrowserImpl(const base::CommandLine& command_line) {
  TRACE_EVENT0("startup", "upgrade_util::RelaunchChromeBrowserImpl");

  base::FilePath chrome_exe;
  if (!base::PathService::Get(base::FILE_EXE, &chrome_exe)) {
    NOTREACHED();
  }

  // Explicitly make sure to relaunch chrome.exe rather than old_chrome.exe.
  // This can happen when old_chrome.exe is launched by a user.
  base::CommandLine chrome_exe_command_line = command_line;
  chrome_exe_command_line.SetProgram(
      chrome_exe.DirName().Append(installer::kChromeExe));

  // Set the working directory to the exe's directory. This avoids a handle to
  // the version directory being kept open in the relaunched child process.
  base::LaunchOptions launch_options;
  launch_options.current_directory = chrome_exe.DirName();
  // Give the new process the right to bring its windows to the foreground.
  launch_options.grant_foreground_privilege = true;
  return base::LaunchProcess(chrome_exe_command_line, launch_options).IsValid();
}

bool IsUpdatePendingRestart() {
  TRACE_EVENT0("startup", "upgrade_util::IsUpdatePendingRestart");
  base::FilePath new_chrome_exe;
  if (!GetNewerChromeFile(&new_chrome_exe))
    return false;
  return base::PathExists(new_chrome_exe);
}

bool SwapNewChromeExeIfPresent() {
  if (!IsUpdatePendingRestart())
    return false;

  TRACE_EVENT0("startup", "upgrade_util::SwapNewChromeExeIfPresent");

  // Renaming the chrome executable requires the process singleton to avoid
  // any race condition.
  CHECK(ChromeProcessSingleton::IsSingletonInstance());

  // If this is a system-level install, ask Google Update to launch an elevated
  // process to rename Chrome executables.
  if (install_static::IsSystemInstall())
    return InvokeGoogleUpdateForRename();

  // If this is a user-level install, directly launch a process to rename Chrome
  // executables. Obtain the command to launch the process from the registry.
  installer::AppCommand rename_cmd(installer::kCmdRenameChromeExe, {});
  if (!rename_cmd.Initialize(HKEY_CURRENT_USER))
    return false;

  base::LaunchOptions options;
  options.wait = true;
  options.start_hidden = true;
  ::SetLastError(ERROR_SUCCESS);
  base::Process process =
      base::LaunchProcess(rename_cmd.command_line(), options);
  if (!process.IsValid()) {
    PLOG(ERROR) << "Launch rename process failed";
    return false;
  }

  DWORD exit_code;
  if (!::GetExitCodeProcess(process.Handle(), &exit_code)) {
    PLOG(ERROR) << "GetExitCodeProcess of rename process failed";
    return false;
  }

  if (exit_code != installer::RENAME_SUCCESSFUL) {
    LOG(ERROR) << "Rename process failed with exit code " << exit_code;
    return false;
  }

  return true;
}

bool IsRunningOldChrome() {
  TRACE_EVENT0("startup", "upgrade_util::IsRunningOldChrome");
  // This figures out the actual file name that the section containing the
  // mapped exe refers to. This is used instead of GetModuleFileName because the
  // .exe may have been renamed out from under us while we've been running which
  // GetModuleFileName won't notice.
  wchar_t mapped_file_name[MAX_PATH * 2] = {};

  if (!::GetMappedFileName(::GetCurrentProcess(),
                           reinterpret_cast<void*>(::GetModuleHandle(NULL)),
                           mapped_file_name, std::size(mapped_file_name))) {
    return false;
  }

  base::FilePath file_name(base::FilePath(mapped_file_name).BaseName());
  return base::FilePath::CompareEqualIgnoreCase(file_name.value(),
                                                installer::kChromeOldExe);
}

bool DoUpgradeTasks(const base::CommandLine& command_line) {
  TRACE_EVENT0("startup", "upgrade_util::DoUpgradeTasks");
  // If there is no other instance already running then check if there is a
  // pending update and complete it by performing the swap and then relaunch.

  // Upgrade tasks require the process singleton to avoid any race condition.
  CHECK(ChromeProcessSingleton::IsSingletonInstance());

  bool did_swap = false;
  if (!browser_util::IsBrowserAlreadyRunning())
    did_swap = SwapNewChromeExeIfPresent();

  // We don't need to relaunch if we didn't swap and we aren't running stale
  // binaries.
  if (!did_swap && !IsRunningOldChrome()) {
    return false;
  }

  // At this point the chrome.exe has been swapped with the new one.
  if (!RelaunchChromeBrowser(command_line)) {
    // The relaunch failed. Feel free to panic now.
    DUMP_WILL_BE_NOTREACHED();
  }
  return true;
}

}  // namespace upgrade_util