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.
5 #include "chrome/browser/component_updater/recovery_component_installer.h"
10 #include "base/base_paths.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/compiler_specific.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/json/json_file_value_serializer.h"
17 #include "base/logging.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/metrics/histogram.h"
20 #include "base/path_service.h"
21 #include "base/prefs/pref_registry_simple.h"
22 #include "base/prefs/pref_service.h"
23 #include "base/process/kill.h"
24 #include "base/process/launch.h"
25 #include "base/process/process.h"
26 #include "base/threading/worker_pool.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/component_updater/component_updater_paths.h"
30 #include "components/component_updater/component_updater_service.h"
31 #include "components/component_updater/pref_names.h"
32 #include "components/update_client/update_client.h"
33 #include "content/public/browser/browser_thread.h"
35 using content::BrowserThread
;
37 namespace component_updater
{
41 // CRX hash. The extension id is: npdjjkjlcidkjlamlmmdelcjbcpdjocm.
42 const uint8_t kSha2Hash
[] = {0xdf, 0x39, 0x9a, 0x9b, 0x28, 0x3a, 0x9b, 0x0c,
43 0xbc, 0xc3, 0x4b, 0x29, 0x12, 0xf3, 0x9e, 0x2c,
44 0x19, 0x7a, 0x71, 0x4b, 0x0a, 0x7c, 0x80, 0x1c,
45 0xf6, 0x29, 0x7c, 0x0a, 0x5f, 0xea, 0x67, 0xb7};
47 // File name of the recovery binary on different platforms.
48 const base::FilePath::CharType kRecoveryFileName
[] =
50 FILE_PATH_LITERAL("ChromeRecovery.exe");
51 #else // OS_LINUX, OS_MACOSX, etc.
52 FILE_PATH_LITERAL("ChromeRecovery");
55 const char kRecoveryManifestName
[] = "ChromeRecovery";
57 // ChromeRecovery process exit codes.
58 enum ChromeRecoveryExitCode
{
59 EXIT_CODE_RECOVERY_SUCCEEDED
= 0,
60 EXIT_CODE_RECOVERY_SKIPPED
= 1,
61 EXIT_CODE_ELEVATION_NEEDED
= 2,
64 enum RecoveryComponentEvent
{
65 RCE_RUNNING_NON_ELEVATED
= 0,
66 RCE_ELEVATION_NEEDED
= 1,
70 RCE_RUNNING_ELEVATED
= 5,
71 RCE_ELEVATED_FAILED
= 6,
72 RCE_ELEVATED_SUCCEEDED
= 7,
73 RCE_ELEVATED_SKIPPED
= 8,
74 RCE_COMPONENT_DOWNLOAD_ERROR
= 9,
78 void RecordRecoveryComponentUMAEvent(RecoveryComponentEvent event
) {
79 UMA_HISTOGRAM_ENUMERATION("RecoveryComponent.Event", event
, RCE_COUNT
);
82 #if !defined(OS_CHROMEOS)
83 // Checks if elevated recovery simulation switch was present on the command
84 // line. This is for testing purpose.
85 bool SimulatingElevatedRecovery() {
86 return base::CommandLine::ForCurrentProcess()->HasSwitch(
87 switches::kSimulateElevatedRecovery
);
89 #endif // !defined(OS_CHROMEOS)
91 base::CommandLine
GetRecoveryInstallCommandLine(
92 const base::FilePath
& command
,
93 const base::DictionaryValue
& manifest
,
95 const Version
& version
) {
96 base::CommandLine
command_line(command
);
98 // Add a flag to for re-attempted install with elevated privilege so that the
99 // recovery executable can report back accordingly.
101 command_line
.AppendArg("/deferredrun");
103 std::string arguments
;
104 if (manifest
.GetStringASCII("x-recovery-args", &arguments
))
105 command_line
.AppendArg(arguments
);
106 std::string add_version
;
107 if (manifest
.GetStringASCII("x-recovery-add-version", &add_version
) &&
108 add_version
== "yes") {
109 std::string version_string
= "/version ";
110 version_string
+= version
.GetString();
111 command_line
.AppendArg(version_string
);
118 scoped_ptr
<base::DictionaryValue
> ReadManifest(const base::FilePath
& manifest
) {
119 JSONFileValueDeserializer
deserializer(manifest
);
121 scoped_ptr
<base::Value
> root(deserializer
.Deserialize(NULL
, &error
));
122 if (root
.get() && root
->IsType(base::Value::TYPE_DICTIONARY
)) {
123 return scoped_ptr
<base::DictionaryValue
>(
124 static_cast<base::DictionaryValue
*>(root
.release()));
126 return scoped_ptr
<base::DictionaryValue
>();
129 void WaitForElevatedInstallToComplete(base::Process process
) {
130 int installer_exit_code
= 0;
131 const base::TimeDelta kMaxWaitTime
= base::TimeDelta::FromSeconds(600);
132 if (process
.WaitForExitWithTimeout(kMaxWaitTime
, &installer_exit_code
)) {
133 if (installer_exit_code
== EXIT_CODE_RECOVERY_SUCCEEDED
) {
134 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SUCCEEDED
);
136 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SKIPPED
);
139 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_FAILED
);
143 void DoElevatedInstallRecoveryComponent(const base::FilePath
& path
) {
144 const base::FilePath main_file
= path
.Append(kRecoveryFileName
);
145 const base::FilePath manifest_file
=
146 path
.Append(FILE_PATH_LITERAL("manifest.json"));
147 if (!base::PathExists(main_file
) || !base::PathExists(manifest_file
))
150 scoped_ptr
<base::DictionaryValue
> manifest(ReadManifest(manifest_file
));
152 manifest
->GetStringASCII("name", &name
);
153 if (name
!= kRecoveryManifestName
)
155 std::string proposed_version
;
156 manifest
->GetStringASCII("version", &proposed_version
);
157 const Version
version(proposed_version
.c_str());
158 if (!version
.IsValid())
161 const bool is_deferred_run
= true;
162 const auto cmdline
= GetRecoveryInstallCommandLine(
163 main_file
, *manifest
, is_deferred_run
, version
);
165 RecordRecoveryComponentUMAEvent(RCE_RUNNING_ELEVATED
);
167 base::LaunchOptions options
;
168 options
.start_hidden
= true;
169 base::Process process
= base::LaunchElevatedProcess(cmdline
, options
);
171 base::WorkerPool::PostTask(
173 base::Bind(&WaitForElevatedInstallToComplete
, base::Passed(&process
)),
177 void ElevatedInstallRecoveryComponent(const base::FilePath
& installer_path
) {
178 base::WorkerPool::PostTask(
180 base::Bind(&DoElevatedInstallRecoveryComponent
, installer_path
),
183 #endif // defined(OS_WIN)
187 // Component installer that is responsible to repair the chrome installation
188 // or repair the Google update installation. This is a last resort safety
190 // For user Chrome, recovery component just installs silently. For machine
191 // Chrome, elevation may be needed. If that happens, the installer will set
192 // preference flag prefs::kRecoveryComponentNeedsElevation to request that.
193 // There is a global error service monitors this flag and will pop up
194 // bubble if the flag is set to true.
195 // See chrome/browser/recovery/recovery_install_global_error.cc for details.
196 class RecoveryComponentInstaller
: public update_client::CrxInstaller
{
198 RecoveryComponentInstaller(const Version
& version
, PrefService
* prefs
);
200 // ComponentInstaller implementation:
201 void OnUpdateError(int error
) override
;
203 bool Install(const base::DictionaryValue
& manifest
,
204 const base::FilePath
& unpack_path
) override
;
206 bool GetInstalledFile(const std::string
& file
,
207 base::FilePath
* installed_file
) override
;
209 bool Uninstall() override
;
212 ~RecoveryComponentInstaller() override
{}
214 bool RunInstallCommand(const base::CommandLine
& cmdline
,
215 const base::FilePath
& installer_folder
) const;
217 Version current_version_
;
221 void SimulateElevatedRecoveryHelper(PrefService
* prefs
) {
222 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, true);
225 void RecoveryRegisterHelper(ComponentUpdateService
* cus
, PrefService
* prefs
) {
226 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
227 Version
version(prefs
->GetString(prefs::kRecoveryComponentVersion
));
228 if (!version
.IsValid()) {
233 update_client::CrxComponent recovery
;
234 recovery
.name
= "recovery";
235 recovery
.installer
= new RecoveryComponentInstaller(version
, prefs
);
236 recovery
.version
= version
;
237 recovery
.pk_hash
.assign(kSha2Hash
, &kSha2Hash
[sizeof(kSha2Hash
)]);
238 if (!cus
->RegisterComponent(recovery
)) {
239 NOTREACHED() << "Recovery component registration failed.";
243 void RecoveryUpdateVersionHelper(const Version
& version
, PrefService
* prefs
) {
244 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
245 prefs
->SetString(prefs::kRecoveryComponentVersion
, version
.GetString());
248 void SetPrefsForElevatedRecoveryInstall(const base::FilePath
& unpack_path
,
249 PrefService
* prefs
) {
250 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
251 prefs
->SetFilePath(prefs::kRecoveryComponentUnpackPath
, unpack_path
);
252 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, true);
255 RecoveryComponentInstaller::RecoveryComponentInstaller(const Version
& version
,
257 : current_version_(version
), prefs_(prefs
) {
258 DCHECK(version
.IsValid());
261 void RecoveryComponentInstaller::OnUpdateError(int error
) {
262 RecordRecoveryComponentUMAEvent(RCE_COMPONENT_DOWNLOAD_ERROR
);
263 NOTREACHED() << "Recovery component update error: " << error
;
267 void WaitForInstallToComplete(base::Process process
,
268 const base::FilePath
& installer_folder
,
269 PrefService
* prefs
) {
270 int installer_exit_code
= 0;
271 const base::TimeDelta kMaxWaitTime
= base::TimeDelta::FromSeconds(600);
272 if (process
.WaitForExitWithTimeout(kMaxWaitTime
, &installer_exit_code
)) {
273 if (installer_exit_code
== EXIT_CODE_ELEVATION_NEEDED
) {
274 RecordRecoveryComponentUMAEvent(RCE_ELEVATION_NEEDED
);
276 BrowserThread::PostTask(
279 base::Bind(&SetPrefsForElevatedRecoveryInstall
,
282 } else if (installer_exit_code
== EXIT_CODE_RECOVERY_SUCCEEDED
) {
283 RecordRecoveryComponentUMAEvent(RCE_SUCCEEDED
);
284 } else if (installer_exit_code
== EXIT_CODE_RECOVERY_SKIPPED
) {
285 RecordRecoveryComponentUMAEvent(RCE_SKIPPED
);
288 RecordRecoveryComponentUMAEvent(RCE_FAILED
);
292 bool RecoveryComponentInstaller::RunInstallCommand(
293 const base::CommandLine
& cmdline
,
294 const base::FilePath
& installer_folder
) const {
295 RecordRecoveryComponentUMAEvent(RCE_RUNNING_NON_ELEVATED
);
297 base::LaunchOptions options
;
298 options
.start_hidden
= true;
299 base::Process process
= base::LaunchProcess(cmdline
, options
);
300 if (!process
.IsValid())
303 // Let worker pool thread wait for us so we don't block Chrome shutdown.
304 base::WorkerPool::PostTask(
306 base::Bind(&WaitForInstallToComplete
,
307 base::Passed(&process
), installer_folder
, prefs_
),
310 // Returns true regardless of install result since from updater service
311 // perspective the install is done, even we may need to do elevated
316 bool RecoveryComponentInstaller::RunInstallCommand(
317 const base::CommandLine
& cmdline
,
318 const base::FilePath
&) const {
319 return base::LaunchProcess(cmdline
, base::LaunchOptions()).IsValid();
321 #endif // defined(OS_WIN)
323 bool RecoveryComponentInstaller::Install(const base::DictionaryValue
& manifest
,
324 const base::FilePath
& unpack_path
) {
326 manifest
.GetStringASCII("name", &name
);
327 if (name
!= kRecoveryManifestName
)
329 std::string proposed_version
;
330 manifest
.GetStringASCII("version", &proposed_version
);
331 Version
version(proposed_version
.c_str());
332 if (!version
.IsValid())
334 if (current_version_
.CompareTo(version
) >= 0)
337 // Passed the basic tests. Copy the installation to a permanent directory.
339 if (!PathService::Get(DIR_RECOVERY_BASE
, &path
))
341 if (!base::PathExists(path
) && !base::CreateDirectory(path
))
343 path
= path
.AppendASCII(version
.GetString());
344 if (base::PathExists(path
) && !base::DeleteFile(path
, true))
346 if (!base::Move(unpack_path
, path
)) {
347 DVLOG(1) << "Recovery component move failed.";
351 base::FilePath main_file
= path
.Append(kRecoveryFileName
);
352 if (!base::PathExists(main_file
))
355 // Run the recovery component.
356 const bool is_deferred_run
= false;
357 const auto cmdline
= GetRecoveryInstallCommandLine(
358 main_file
, manifest
, is_deferred_run
, current_version_
);
360 if (!RunInstallCommand(cmdline
, path
)) {
364 current_version_
= version
;
366 BrowserThread::PostTask(
369 base::Bind(&RecoveryUpdateVersionHelper
, version
, prefs_
));
374 bool RecoveryComponentInstaller::GetInstalledFile(
375 const std::string
& file
,
376 base::FilePath
* installed_file
) {
380 bool RecoveryComponentInstaller::Uninstall() {
384 void RegisterRecoveryComponent(ComponentUpdateService
* cus
,
385 PrefService
* prefs
) {
386 #if !defined(OS_CHROMEOS)
387 if (SimulatingElevatedRecovery()) {
388 BrowserThread::PostTask(
391 base::Bind(&SimulateElevatedRecoveryHelper
, prefs
));
394 // We delay execute the registration because we are not required in
395 // the critical path during browser startup.
396 BrowserThread::PostDelayedTask(
399 base::Bind(&RecoveryRegisterHelper
, cus
, prefs
),
400 base::TimeDelta::FromSeconds(6));
401 #endif // !defined(OS_CHROMEOS)
404 void RegisterPrefsForRecoveryComponent(PrefRegistrySimple
* registry
) {
405 registry
->RegisterStringPref(prefs::kRecoveryComponentVersion
, "0.0.0.0");
406 registry
->RegisterFilePathPref(prefs::kRecoveryComponentUnpackPath
,
408 registry
->RegisterBooleanPref(prefs::kRecoveryComponentNeedsElevation
, false);
411 void AcceptedElevatedRecoveryInstall(PrefService
* prefs
) {
412 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
415 ElevatedInstallRecoveryComponent(
416 prefs
->GetFilePath(prefs::kRecoveryComponentUnpackPath
));
418 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, false);
421 void DeclinedElevatedRecoveryInstall(PrefService
* prefs
) {
422 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
423 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, false);
426 } // namespace component_updater