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)
92 scoped_ptr
<base::DictionaryValue
> ReadManifest(const base::FilePath
& manifest
) {
93 JSONFileValueSerializer
serializer(manifest
);
95 scoped_ptr
<base::Value
> root(serializer
.Deserialize(NULL
, &error
));
96 if (root
.get() && root
->IsType(base::Value::TYPE_DICTIONARY
)) {
97 return scoped_ptr
<base::DictionaryValue
>(
98 static_cast<base::DictionaryValue
*>(root
.release()));
100 return scoped_ptr
<base::DictionaryValue
>();
103 void WaitForElevatedInstallToComplete(base::Process process
) {
104 int installer_exit_code
= 0;
105 const base::TimeDelta kMaxWaitTime
= base::TimeDelta::FromSeconds(600);
106 if (process
.WaitForExitWithTimeout(kMaxWaitTime
, &installer_exit_code
)) {
107 if (installer_exit_code
== EXIT_CODE_RECOVERY_SUCCEEDED
) {
108 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SUCCEEDED
);
110 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_SKIPPED
);
113 RecordRecoveryComponentUMAEvent(RCE_ELEVATED_FAILED
);
117 void DoElevatedInstallRecoveryComponent(const base::FilePath
& path
) {
118 const base::FilePath main_file
= path
.Append(kRecoveryFileName
);
119 const base::FilePath manifest_file
=
120 path
.Append(FILE_PATH_LITERAL("manifest.json"));
121 if (!base::PathExists(main_file
) || !base::PathExists(manifest_file
))
124 scoped_ptr
<base::DictionaryValue
> manifest(ReadManifest(manifest_file
));
126 manifest
->GetStringASCII("name", &name
);
127 if (name
!= kRecoveryManifestName
)
129 std::string proposed_version
;
130 manifest
->GetStringASCII("version", &proposed_version
);
131 const Version
version(proposed_version
.c_str());
132 if (!version
.IsValid())
135 base::CommandLine
cmdline(main_file
);
136 std::string arguments
;
137 if (manifest
->GetStringASCII("x-recovery-args", &arguments
))
138 cmdline
.AppendArg(arguments
);
139 std::string add_version
;
140 if (manifest
->GetStringASCII("x-recovery-add-version", &add_version
) &&
141 add_version
== "yes") {
142 cmdline
.AppendSwitchASCII("version", version
.GetString());
145 RecordRecoveryComponentUMAEvent(RCE_RUNNING_ELEVATED
);
147 base::LaunchOptions options
;
148 options
.start_hidden
= true;
149 base::Process process
= base::LaunchElevatedProcess(cmdline
, options
);
151 base::WorkerPool::PostTask(
153 base::Bind(&WaitForElevatedInstallToComplete
, base::Passed(&process
)),
157 void ElevatedInstallRecoveryComponent(const base::FilePath
& installer_path
) {
158 base::WorkerPool::PostTask(
160 base::Bind(&DoElevatedInstallRecoveryComponent
, installer_path
),
163 #endif // defined(OS_WIN)
167 // Component installer that is responsible to repair the chrome installation
168 // or repair the Google update installation. This is a last resort safety
170 // For user Chrome, recovery component just installs silently. For machine
171 // Chrome, elevation may be needed. If that happens, the installer will set
172 // preference flag prefs::kRecoveryComponentNeedsElevation to request that.
173 // There is a global error service monitors this flag and will pop up
174 // bubble if the flag is set to true.
175 // See chrome/browser/recovery/recovery_install_global_error.cc for details.
176 class RecoveryComponentInstaller
: public update_client::ComponentInstaller
{
178 RecoveryComponentInstaller(const Version
& version
, PrefService
* prefs
);
180 // ComponentInstaller implementation:
181 void OnUpdateError(int error
) override
;
183 bool Install(const base::DictionaryValue
& manifest
,
184 const base::FilePath
& unpack_path
) override
;
186 bool GetInstalledFile(const std::string
& file
,
187 base::FilePath
* installed_file
) override
;
189 bool Uninstall() override
;
192 ~RecoveryComponentInstaller() override
{}
194 bool RunInstallCommand(const base::CommandLine
& cmdline
,
195 const base::FilePath
& installer_folder
) const;
197 Version current_version_
;
201 void SimulateElevatedRecoveryHelper(PrefService
* prefs
) {
202 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, true);
205 void RecoveryRegisterHelper(ComponentUpdateService
* cus
, PrefService
* prefs
) {
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
207 Version
version(prefs
->GetString(prefs::kRecoveryComponentVersion
));
208 if (!version
.IsValid()) {
213 update_client::CrxComponent recovery
;
214 recovery
.name
= "recovery";
215 recovery
.installer
= new RecoveryComponentInstaller(version
, prefs
);
216 recovery
.version
= version
;
217 recovery
.pk_hash
.assign(kSha2Hash
, &kSha2Hash
[sizeof(kSha2Hash
)]);
218 if (cus
->RegisterComponent(recovery
) != ComponentUpdateService::kOk
) {
219 NOTREACHED() << "Recovery component registration failed.";
223 void RecoveryUpdateVersionHelper(const Version
& version
, PrefService
* prefs
) {
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
225 prefs
->SetString(prefs::kRecoveryComponentVersion
, version
.GetString());
228 void SetPrefsForElevatedRecoveryInstall(const base::FilePath
& unpack_path
,
229 PrefService
* prefs
) {
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
231 prefs
->SetFilePath(prefs::kRecoveryComponentUnpackPath
, unpack_path
);
232 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, true);
235 RecoveryComponentInstaller::RecoveryComponentInstaller(const Version
& version
,
237 : current_version_(version
), prefs_(prefs
) {
238 DCHECK(version
.IsValid());
241 void RecoveryComponentInstaller::OnUpdateError(int error
) {
242 RecordRecoveryComponentUMAEvent(RCE_COMPONENT_DOWNLOAD_ERROR
);
243 NOTREACHED() << "Recovery component update error: " << error
;
247 void WaitForInstallToComplete(base::Process process
,
248 const base::FilePath
& installer_folder
,
249 PrefService
* prefs
) {
250 int installer_exit_code
= 0;
251 const base::TimeDelta kMaxWaitTime
= base::TimeDelta::FromSeconds(600);
252 if (process
.WaitForExitWithTimeout(kMaxWaitTime
, &installer_exit_code
)) {
253 if (installer_exit_code
== EXIT_CODE_ELEVATION_NEEDED
) {
254 RecordRecoveryComponentUMAEvent(RCE_ELEVATION_NEEDED
);
256 BrowserThread::PostTask(
259 base::Bind(&SetPrefsForElevatedRecoveryInstall
,
262 } else if (installer_exit_code
== EXIT_CODE_RECOVERY_SUCCEEDED
) {
263 RecordRecoveryComponentUMAEvent(RCE_SUCCEEDED
);
264 } else if (installer_exit_code
== EXIT_CODE_RECOVERY_SKIPPED
) {
265 RecordRecoveryComponentUMAEvent(RCE_SKIPPED
);
268 RecordRecoveryComponentUMAEvent(RCE_FAILED
);
272 bool RecoveryComponentInstaller::RunInstallCommand(
273 const base::CommandLine
& cmdline
,
274 const base::FilePath
& installer_folder
) const {
275 RecordRecoveryComponentUMAEvent(RCE_RUNNING_NON_ELEVATED
);
277 base::LaunchOptions options
;
278 options
.start_hidden
= true;
279 base::Process process
= base::LaunchProcess(cmdline
, options
);
280 if (!process
.IsValid())
283 // Let worker pool thread wait for us so we don't block Chrome shutdown.
284 base::WorkerPool::PostTask(
286 base::Bind(&WaitForInstallToComplete
,
287 base::Passed(&process
), installer_folder
, prefs_
),
290 // Returns true regardless of install result since from updater service
291 // perspective the install is done, even we may need to do elevated
296 bool RecoveryComponentInstaller::RunInstallCommand(
297 const base::CommandLine
& cmdline
,
298 const base::FilePath
&) const {
299 return base::LaunchProcess(cmdline
, base::LaunchOptions()).IsValid();
301 #endif // defined(OS_WIN)
303 bool RecoveryComponentInstaller::Install(const base::DictionaryValue
& manifest
,
304 const base::FilePath
& unpack_path
) {
306 manifest
.GetStringASCII("name", &name
);
307 if (name
!= kRecoveryManifestName
)
309 std::string proposed_version
;
310 manifest
.GetStringASCII("version", &proposed_version
);
311 Version
version(proposed_version
.c_str());
312 if (!version
.IsValid())
314 if (current_version_
.CompareTo(version
) >= 0)
317 // Passed the basic tests. Copy the installation to a permanent directory.
319 if (!PathService::Get(DIR_RECOVERY_BASE
, &path
))
321 if (!base::PathExists(path
) && !base::CreateDirectory(path
))
323 path
= path
.AppendASCII(version
.GetString());
324 if (base::PathExists(path
) && !base::DeleteFile(path
, true))
326 if (!base::Move(unpack_path
, path
)) {
327 DVLOG(1) << "Recovery component move failed.";
331 base::FilePath main_file
= path
.Append(kRecoveryFileName
);
332 if (!base::PathExists(main_file
))
334 // Run the recovery component.
335 base::CommandLine
cmdline(main_file
);
336 std::string arguments
;
337 if (manifest
.GetStringASCII("x-recovery-args", &arguments
))
338 cmdline
.AppendArg(arguments
);
339 std::string add_version
;
340 if (manifest
.GetStringASCII("x-recovery-add-version", &add_version
) &&
341 add_version
== "yes") {
342 cmdline
.AppendSwitchASCII("version", current_version_
.GetString());
345 if (!RunInstallCommand(cmdline
, path
)) {
349 current_version_
= version
;
351 BrowserThread::PostTask(
354 base::Bind(&RecoveryUpdateVersionHelper
, version
, prefs_
));
359 bool RecoveryComponentInstaller::GetInstalledFile(
360 const std::string
& file
,
361 base::FilePath
* installed_file
) {
365 bool RecoveryComponentInstaller::Uninstall() {
369 void RegisterRecoveryComponent(ComponentUpdateService
* cus
,
370 PrefService
* prefs
) {
371 #if !defined(OS_CHROMEOS)
372 if (SimulatingElevatedRecovery()) {
373 BrowserThread::PostTask(
376 base::Bind(&SimulateElevatedRecoveryHelper
, prefs
));
379 // We delay execute the registration because we are not required in
380 // the critical path during browser startup.
381 BrowserThread::PostDelayedTask(
384 base::Bind(&RecoveryRegisterHelper
, cus
, prefs
),
385 base::TimeDelta::FromSeconds(6));
386 #endif // !defined(OS_CHROMEOS)
389 void RegisterPrefsForRecoveryComponent(PrefRegistrySimple
* registry
) {
390 registry
->RegisterStringPref(prefs::kRecoveryComponentVersion
, "0.0.0.0");
391 registry
->RegisterFilePathPref(prefs::kRecoveryComponentUnpackPath
,
393 registry
->RegisterBooleanPref(prefs::kRecoveryComponentNeedsElevation
, false);
396 void AcceptedElevatedRecoveryInstall(PrefService
* prefs
) {
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
400 ElevatedInstallRecoveryComponent(
401 prefs
->GetFilePath(prefs::kRecoveryComponentUnpackPath
));
403 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, false);
406 void DeclinedElevatedRecoveryInstall(PrefService
* prefs
) {
407 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
408 prefs
->SetBoolean(prefs::kRecoveryComponentNeedsElevation
, false);
411 } // namespace component_updater