1 // Copyright 2017 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/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.h"
6
7 #include <windows.h>
8
9 #include <set>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/bind.h"
15 #include "base/callback.h"
16 #include "base/callback_helpers.h"
17 #include "base/files/file_util.h"
18 #include "base/location.h"
19 #include "base/logging.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/task/task_traits.h"
22 #include "base/task/thread_pool.h"
23 #include "base/threading/thread_task_runner_handle.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/component_updater/sw_reporter_installer_win.h"
26 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
27 #include "chrome/browser/net/system_network_context_manager.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.h"
31 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_navigation_util_win.h"
32 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_reboot_dialog_controller_impl_win.h"
33 #include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_runner_win.h"
34 #include "chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.h"
35 #include "chrome/browser/safe_browsing/chrome_cleaner/settings_resetter_win.h"
36 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h"
37 #include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h"
38 #include "chrome/browser/ui/browser.h"
39 #include "chrome/common/pref_names.h"
40 #include "chrome/installer/util/scoped_token_privilege.h"
41 #include "components/chrome_cleaner/public/constants/constants.h"
42 #include "components/chrome_cleaner/public/proto/chrome_prompt.pb.h"
43 #include "components/component_updater/component_updater_service.h"
44 #include "components/component_updater/pref_names.h"
45 #include "components/prefs/pref_service.h"
46 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
47 #include "content/public/browser/browser_task_traits.h"
48 #include "content/public/browser/browser_thread.h"
49 #include "extensions/browser/extension_registry.h"
50 #include "net/http/http_status_code.h"
51 #include "ui/base/window_open_disposition.h"
52
53 namespace safe_browsing {
54
55 namespace {
56
57 using ::content::BrowserThread;
58 using PromptUserResponse = chrome_cleaner::PromptUserResponse;
59
60 // The global singleton instance. Exposed outside of GetInstance() so that it
61 // can be reset by tests.
62 ChromeCleanerControllerImpl* g_controller = nullptr;
63
64 // TODO(alito): Move these shared exit codes to the chrome_cleaner component.
65 // https://crbug.com/727956
66 constexpr int kRebootRequiredExitCode = 15;
67 constexpr int kRebootNotRequiredExitCode = 0;
68
69 // These values are used to send UMA information and are replicated in the
70 // enums.xml file, so the order MUST NOT CHANGE.
71 enum CleanupResultHistogramValue {
72 CLEANUP_RESULT_SUCCEEDED = 0,
73 CLEANUP_RESULT_REBOOT_REQUIRED = 1,
74 CLEANUP_RESULT_FAILED = 2,
75
76 CLEANUP_RESULT_MAX,
77 };
78
79 // These values are used to send UMA information and are replicated in the
80 // enums.xml file, so the order MUST NOT CHANGE.
81 enum IPCDisconnectedHistogramValue {
82 IPC_DISCONNECTED_SUCCESS = 0,
83 IPC_DISCONNECTED_LOST_WHILE_SCANNING = 1,
84 IPC_DISCONNECTED_LOST_USER_PROMPTED = 2,
85
86 IPC_DISCONNECTED_MAX,
87 };
88
89 // Attempts to change the Chrome Cleaner binary's suffix to ".exe". Will return
90 // an empty FilePath on failure. Should be called on a sequence with traits
91 // appropriate for IO operations.
VerifyAndRenameDownloadedCleaner(base::FilePath downloaded_path,ChromeCleanerFetchStatus fetch_status)92 base::FilePath VerifyAndRenameDownloadedCleaner(
93 base::FilePath downloaded_path,
94 ChromeCleanerFetchStatus fetch_status) {
95 if (downloaded_path.empty() || !base::PathExists(downloaded_path))
96 return base::FilePath();
97
98 if (fetch_status != ChromeCleanerFetchStatus::kSuccess) {
99 base::DeleteFile(downloaded_path);
100 return base::FilePath();
101 }
102
103 base::FilePath executable_path(
104 downloaded_path.ReplaceExtension(FILE_PATH_LITERAL("exe")));
105
106 if (!base::ReplaceFile(downloaded_path, executable_path, nullptr)) {
107 base::DeleteFile(downloaded_path);
108 return base::FilePath();
109 }
110
111 return executable_path;
112 }
113
OnChromeCleanerFetched(ChromeCleanerControllerDelegate::FetchedCallback fetched_callback,base::FilePath downloaded_path,ChromeCleanerFetchStatus fetch_status)114 void OnChromeCleanerFetched(
115 ChromeCleanerControllerDelegate::FetchedCallback fetched_callback,
116 base::FilePath downloaded_path,
117 ChromeCleanerFetchStatus fetch_status) {
118 base::ThreadPool::PostTaskAndReplyWithResult(
119 FROM_HERE,
120 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
121 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
122 base::BindOnce(VerifyAndRenameDownloadedCleaner, downloaded_path,
123 fetch_status),
124 std::move(fetched_callback));
125 }
126
IdleReasonWhenConnectionClosedTooSoon(ChromeCleanerController::State current_state)127 ChromeCleanerController::IdleReason IdleReasonWhenConnectionClosedTooSoon(
128 ChromeCleanerController::State current_state) {
129 DCHECK(current_state == ChromeCleanerController::State::kScanning ||
130 current_state == ChromeCleanerController::State::kInfected);
131
132 return current_state == ChromeCleanerController::State::kScanning
133 ? ChromeCleanerController::IdleReason::kScanningFailed
134 : ChromeCleanerController::IdleReason::kConnectionLost;
135 }
136
RecordScannerLogsAcceptanceHistogram(bool logs_accepted)137 void RecordScannerLogsAcceptanceHistogram(bool logs_accepted) {
138 UMA_HISTOGRAM_BOOLEAN("SoftwareReporter.ScannerLogsAcceptance",
139 logs_accepted);
140 }
141
RecordCleanerLogsAcceptanceHistogram(bool logs_accepted)142 void RecordCleanerLogsAcceptanceHistogram(bool logs_accepted) {
143 UMA_HISTOGRAM_BOOLEAN("SoftwareReporter.CleanerLogsAcceptance",
144 logs_accepted);
145 }
146
RecordCleanupResultHistogram(CleanupResultHistogramValue result)147 void RecordCleanupResultHistogram(CleanupResultHistogramValue result) {
148 UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.Cleaner.CleanupResult", result,
149 CLEANUP_RESULT_MAX);
150 }
151
RecordIPCDisconnectedHistogram(IPCDisconnectedHistogramValue error)152 void RecordIPCDisconnectedHistogram(IPCDisconnectedHistogramValue error) {
153 UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.IPCDisconnected", error,
154 IPC_DISCONNECTED_MAX);
155 }
156
RecordReporterSequenceTypeHistogram(SwReporterInvocationType invocation_type)157 void RecordReporterSequenceTypeHistogram(
158 SwReporterInvocationType invocation_type) {
159 UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.ReporterSequenceType",
160 static_cast<int>(invocation_type),
161 static_cast<int>(SwReporterInvocationType::kMax));
162 }
163
RecordOnDemandUpdateRequiredHistogram(bool value)164 void RecordOnDemandUpdateRequiredHistogram(bool value) {
165 UMA_HISTOGRAM_BOOLEAN("SoftwareReporter.OnDemandUpdateRequired", value);
166 }
167
168 } // namespace
169
170 ChromeCleanerControllerDelegate::ChromeCleanerControllerDelegate() = default;
171
172 ChromeCleanerControllerDelegate::~ChromeCleanerControllerDelegate() = default;
173
FetchAndVerifyChromeCleaner(FetchedCallback fetched_callback)174 void ChromeCleanerControllerDelegate::FetchAndVerifyChromeCleaner(
175 FetchedCallback fetched_callback) {
176 FetchChromeCleaner(
177 base::BindOnce(&OnChromeCleanerFetched, std::move(fetched_callback)),
178 g_browser_process->system_network_context_manager()
179 ->GetURLLoaderFactory());
180 }
181
IsMetricsAndCrashReportingEnabled()182 bool ChromeCleanerControllerDelegate::IsMetricsAndCrashReportingEnabled() {
183 return ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled();
184 }
185
TagForResetting(Profile * profile)186 void ChromeCleanerControllerDelegate::TagForResetting(Profile* profile) {
187 if (PostCleanupSettingsResetter::IsEnabled())
188 PostCleanupSettingsResetter().TagForResetting(profile);
189 }
190
ResetTaggedProfiles(std::vector<Profile * > profiles,base::OnceClosure continuation)191 void ChromeCleanerControllerDelegate::ResetTaggedProfiles(
192 std::vector<Profile*> profiles,
193 base::OnceClosure continuation) {
194 if (PostCleanupSettingsResetter::IsEnabled()) {
195 PostCleanupSettingsResetter().ResetTaggedProfiles(
196 std::move(profiles), std::move(continuation),
197 std::make_unique<PostCleanupSettingsResetter::Delegate>());
198 }
199 }
200
StartRebootPromptFlow(ChromeCleanerController * controller)201 void ChromeCleanerControllerDelegate::StartRebootPromptFlow(
202 ChromeCleanerController* controller) {
203 // The controller object decides if and when a prompt should be shown.
204 ChromeCleanerRebootDialogControllerImpl::Create(controller);
205 }
206
IsAllowedByPolicy()207 bool ChromeCleanerControllerDelegate::IsAllowedByPolicy() {
208 return safe_browsing::SwReporterIsAllowedByPolicy();
209 }
210
211 // static
GetInstance()212 ChromeCleanerControllerImpl* ChromeCleanerControllerImpl::GetInstance() {
213 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
214
215 if (!g_controller) {
216 g_controller = new ChromeCleanerControllerImpl();
217 }
218
219 return g_controller;
220 }
221
222 // static
GetInstance()223 ChromeCleanerController* ChromeCleanerController::GetInstance() {
224 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
225 return ChromeCleanerControllerImpl::GetInstance();
226 }
227
state() const228 ChromeCleanerController::State ChromeCleanerControllerImpl::state() const {
229 return state_;
230 }
231
idle_reason() const232 ChromeCleanerController::IdleReason ChromeCleanerControllerImpl::idle_reason()
233 const {
234 return idle_reason_;
235 }
236
SetLogsEnabled(Profile * profile,bool logs_enabled)237 void ChromeCleanerControllerImpl::SetLogsEnabled(Profile* profile,
238 bool logs_enabled) {
239 PrefService* profile_prefs = profile->GetPrefs();
240 profile_prefs->SetBoolean(prefs::kSwReporterReportingEnabled, logs_enabled);
241 }
242
logs_enabled(Profile * profile) const243 bool ChromeCleanerControllerImpl::logs_enabled(Profile* profile) const {
244 PrefService* profile_prefs = profile->GetPrefs();
245 return profile_prefs->GetBoolean(prefs::kSwReporterReportingEnabled);
246 }
247
ResetIdleState()248 void ChromeCleanerControllerImpl::ResetIdleState() {
249 if (state() != State::kIdle || idle_reason() == IdleReason::kInitial)
250 return;
251
252 idle_reason_ = IdleReason::kInitial;
253
254 // SetStateAndNotifyObservers doesn't allow transitions to the same state.
255 // Notify observers directly instead.
256 for (auto& observer : observer_list_)
257 NotifyObserver(&observer);
258 }
259
SetDelegateForTesting(ChromeCleanerControllerDelegate * delegate)260 void ChromeCleanerControllerImpl::SetDelegateForTesting(
261 ChromeCleanerControllerDelegate* delegate) {
262 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
263 delegate_ = delegate ? delegate : real_delegate_.get();
264 DCHECK(delegate_);
265 }
266
SetStateForTesting(State state)267 void ChromeCleanerControllerImpl::SetStateForTesting(State state) {
268 state_ = state;
269 if (state_ == State::kIdle)
270 idle_reason_ = IdleReason::kInitial;
271 }
272
SetIdleForTesting(IdleReason idle_reason)273 void ChromeCleanerControllerImpl::SetIdleForTesting(IdleReason idle_reason) {
274 state_ = State::kIdle;
275 idle_reason_ = idle_reason;
276 }
277
278 // static
ResetInstanceForTesting()279 void ChromeCleanerControllerImpl::ResetInstanceForTesting() {
280 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
281
282 if (g_controller) {
283 delete g_controller;
284 g_controller = nullptr;
285 }
286 }
287
AddObserver(Observer * observer)288 void ChromeCleanerControllerImpl::AddObserver(Observer* observer) {
289 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
290 observer_list_.AddObserver(observer);
291 NotifyObserver(observer);
292 }
293
RemoveObserver(Observer * observer)294 void ChromeCleanerControllerImpl::RemoveObserver(Observer* observer) {
295 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
296 observer_list_.RemoveObserver(observer);
297 }
298
HasObserver(Observer * observer)299 bool ChromeCleanerControllerImpl::HasObserver(Observer* observer) {
300 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
301 return observer_list_.HasObserver(observer);
302 }
303
OnReporterSequenceStarted()304 void ChromeCleanerControllerImpl::OnReporterSequenceStarted() {
305 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
306
307 RecordReporterSequenceTypeHistogram(pending_invocation_type_);
308
309 if (state() == State::kIdle)
310 SetStateAndNotifyObservers(State::kReporterRunning);
311 }
312
OnReporterSequenceDone(SwReporterInvocationResult result)313 void ChromeCleanerControllerImpl::OnReporterSequenceDone(
314 SwReporterInvocationResult result) {
315 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
316 DCHECK_NE(SwReporterInvocationResult::kUnspecified, result);
317
318 // Ignore if any interaction with cleaner runs is ongoing. This can happen
319 // in two situations:
320 // - The controller is currently handling the cleanup flow (states: infected,
321 // cleaning, reboot required);
322 // - The controller was handling the cleanup flow when the reporter sequence
323 // started, and we didn't transition to the reporter running state.
324 //
325 // That situation can happen, for example, if a new version of the reporter
326 // component becomes available while the controller is handling the cleanup
327 // flow. The UI should block any attempt of starting a new user-initiated scan
328 // if the controller is not on an idle state, which includes when a reporter
329 // sequence is currently running.
330 if (state() != State::kReporterRunning)
331 return;
332
333 switch (result) {
334 case SwReporterInvocationResult::kNotScheduled:
335 // This can happen if a new periodic reporter run tried to start (for
336 // example, because a new reporter component version became available) and
337 // there is another reporter sequence currently running.
338 // Ignore and wait until the other sequence completes to update state.
339 return;
340
341 case SwReporterInvocationResult::kTimedOut:
342 case SwReporterInvocationResult::kComponentNotAvailable:
343 case SwReporterInvocationResult::kProcessFailedToLaunch:
344 case SwReporterInvocationResult::kGeneralFailure:
345 idle_reason_ = IdleReason::kReporterFailed;
346 break;
347
348 case SwReporterInvocationResult::kNothingFound:
349 case SwReporterInvocationResult::kCleanupNotOffered: {
350 // TODO(crbug.com/1139806): The scan completion timestamp is not always
351 // written to the registry. As a workaround, write the completion
352 // timestamp also to a pref. This ensures that the timestamp is preserved
353 // in case Chrome is still opened when the scan completes. Remove this
354 // workaround once the timestamp is written to the registry in all cases.
355 PrefService* pref_service = g_browser_process->local_state();
356 if (pref_service) {
357 pref_service->SetTime(prefs::kChromeCleanerScanCompletionTime,
358 base::Time::Now());
359 }
360 idle_reason_ = IdleReason::kReporterFoundNothing;
361 break;
362 }
363
364 case SwReporterInvocationResult::kCleanupToBeOffered:
365 // A request to scan will immediately follow this message, so no state
366 // transition will be needed.
367 return;
368
369 default:
370 NOTREACHED();
371 }
372
373 SetStateAndNotifyObservers(State::kIdle);
374 }
375
OnSwReporterReady(SwReporterInvocationSequence && invocations)376 void ChromeCleanerControllerImpl::OnSwReporterReady(
377 SwReporterInvocationSequence&& invocations) {
378 DCHECK_CURRENTLY_ON(BrowserThread::UI);
379 DCHECK(!invocations.container().empty());
380
381 SwReporterInvocationType invocation_type =
382 SwReporterInvocationType::kPeriodicRun;
383 {
384 base::AutoLock autolock(lock_);
385 // Cache a copy of the invocations.
386 cached_reporter_invocations_ =
387 std::make_unique<SwReporterInvocationSequence>(invocations);
388 std::swap(pending_invocation_type_, invocation_type);
389 }
390 safe_browsing::MaybeStartSwReporter(invocation_type, std::move(invocations));
391 }
392
RequestUserInitiatedScan(Profile * profile)393 void ChromeCleanerControllerImpl::RequestUserInitiatedScan(Profile* profile) {
394 base::AutoLock autolock(lock_);
395 DCHECK(IsAllowedByPolicy());
396 DCHECK(pending_invocation_type_ !=
397 SwReporterInvocationType::kUserInitiatedWithLogsAllowed &&
398 pending_invocation_type_ !=
399 SwReporterInvocationType::kUserInitiatedWithLogsDisallowed);
400
401 const bool logs_enabled = this->logs_enabled(profile);
402 RecordScannerLogsAcceptanceHistogram(logs_enabled);
403
404 SwReporterInvocationType invocation_type =
405 logs_enabled ? SwReporterInvocationType::kUserInitiatedWithLogsAllowed
406 : SwReporterInvocationType::kUserInitiatedWithLogsDisallowed;
407
408 if (cached_reporter_invocations_) {
409 SwReporterInvocationSequence copied_sequence(*cached_reporter_invocations_);
410
411 content::GetUIThreadTaskRunner({})->PostTask(
412 FROM_HERE,
413 base::BindOnce(
414 &safe_browsing::MaybeStartSwReporter, invocation_type,
415 // The invocations will be modified by the |ReporterRunner|.
416 // Give it a copy to keep the cached invocations pristine.
417 std::move(copied_sequence)));
418
419 RecordOnDemandUpdateRequiredHistogram(false);
420 } else {
421 pending_invocation_type_ = invocation_type;
422 OnReporterSequenceStarted();
423
424 // Creation of the |SwReporterOnDemandFetcher| automatically starts fetching
425 // the SwReporter component. |OnSwReporterReady| will be called if the
426 // component is successfully installed. Otherwise, |OnReporterSequenceDone|
427 // will be called.
428 on_demand_sw_reporter_fetcher_ =
429 std::make_unique<component_updater::SwReporterOnDemandFetcher>(
430 g_browser_process->component_updater(),
431 base::BindOnce(&ChromeCleanerController::OnReporterSequenceDone,
432 base::Unretained(this),
433 SwReporterInvocationResult::kComponentNotAvailable));
434
435 RecordOnDemandUpdateRequiredHistogram(true);
436 }
437 }
438
Scan(const SwReporterInvocation & reporter_invocation)439 void ChromeCleanerControllerImpl::Scan(
440 const SwReporterInvocation& reporter_invocation) {
441 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
442 DCHECK(IsAllowedByPolicy());
443 DCHECK(reporter_invocation.BehaviourIsSupported(
444 SwReporterInvocation::BEHAVIOUR_TRIGGER_PROMPT));
445
446 if (state() != State::kIdle && state() != State::kReporterRunning)
447 return;
448
449 DCHECK(!reporter_invocation_);
450 reporter_invocation_ =
451 std::make_unique<SwReporterInvocation>(reporter_invocation);
452
453 const std::string& reporter_engine =
454 reporter_invocation_->command_line().GetSwitchValueASCII(
455 chrome_cleaner::kEngineSwitch);
456 // Currently, only engine=2 corresponds to a partner-powered engine. This
457 // condition should be updated if other partner-powered engines are added.
458 powered_by_partner_ = !reporter_engine.empty() && reporter_engine == "2";
459
460 SetStateAndNotifyObservers(State::kScanning);
461 // base::Unretained is safe because the ChromeCleanerController instance is
462 // guaranteed to outlive the UI thread.
463 delegate_->FetchAndVerifyChromeCleaner(base::BindOnce(
464 &ChromeCleanerControllerImpl::OnChromeCleanerFetchedAndVerified,
465 base::Unretained(this)));
466 }
467
ReplyWithUserResponse(Profile * profile,extensions::ExtensionService * extension_service,UserResponse user_response)468 void ChromeCleanerControllerImpl::ReplyWithUserResponse(
469 Profile* profile,
470 extensions::ExtensionService* extension_service,
471 UserResponse user_response) {
472 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
473
474 if (state() != State::kInfected)
475 return;
476
477 DCHECK(prompt_user_reply_callback_);
478
479 PromptUserResponse::PromptAcceptance acceptance = PromptUserResponse::DENIED;
480 State new_state = State::kIdle;
481 switch (user_response) {
482 case UserResponse::kAcceptedWithLogs:
483 acceptance = PromptUserResponse::ACCEPTED_WITH_LOGS;
484 SetLogsEnabled(profile, true);
485 RecordCleanerLogsAcceptanceHistogram(true);
486 new_state = State::kCleaning;
487 delegate_->TagForResetting(profile);
488 extension_service_ = extension_service;
489 extension_registry_ = extensions::ExtensionRegistry::Get(profile);
490 break;
491 case UserResponse::kAcceptedWithoutLogs:
492 acceptance = PromptUserResponse::ACCEPTED_WITHOUT_LOGS;
493 SetLogsEnabled(profile, false);
494 RecordCleanerLogsAcceptanceHistogram(false);
495 new_state = State::kCleaning;
496 delegate_->TagForResetting(profile);
497 extension_service_ = extension_service;
498 extension_registry_ = extensions::ExtensionRegistry::Get(profile);
499 break;
500 case UserResponse::kDenied: // Fallthrough
501 case UserResponse::kDismissed:
502 acceptance = PromptUserResponse::DENIED;
503 idle_reason_ = IdleReason::kUserDeclinedCleanup;
504 new_state = State::kIdle;
505 break;
506 }
507
508 std::move(prompt_user_reply_callback_).Run(acceptance);
509
510 if (new_state == State::kCleaning)
511 time_cleanup_started_ = base::Time::Now();
512
513 // The transition to a new state should happen only after the response has
514 // been posted on the UI thread so that if we transition to the kIdle state,
515 // the response callback is not cleared before it has been posted.
516 SetStateAndNotifyObservers(new_state);
517 }
518
Reboot()519 void ChromeCleanerControllerImpl::Reboot() {
520 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
521
522 if (state() != State::kRebootRequired)
523 return;
524
525 UMA_HISTOGRAM_BOOLEAN("SoftwareReporter.Cleaner.RebootResponse", true);
526 InitiateReboot();
527 }
528
IsAllowedByPolicy()529 bool ChromeCleanerControllerImpl::IsAllowedByPolicy() {
530 return delegate_->IsAllowedByPolicy();
531 }
532
IsReportingManagedByPolicy(Profile * profile)533 bool ChromeCleanerControllerImpl::IsReportingManagedByPolicy(Profile* profile) {
534 // Logs are considered managed if the logs themselves are managed or if the
535 // entire cleanup feature is disabled by policy.
536 PrefService* profile_prefs = profile->GetPrefs();
537 return !IsAllowedByPolicy() ||
538 (profile_prefs && profile_prefs->IsManagedPreference(
539 prefs::kSwReporterReportingEnabled));
540 }
541
ChromeCleanerControllerImpl()542 ChromeCleanerControllerImpl::ChromeCleanerControllerImpl()
543 : real_delegate_(std::make_unique<ChromeCleanerControllerDelegate>()),
544 delegate_(real_delegate_.get()) {
545 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
546 }
547
548 ChromeCleanerControllerImpl::~ChromeCleanerControllerImpl() = default;
549
NotifyObserver(Observer * observer) const550 void ChromeCleanerControllerImpl::NotifyObserver(Observer* observer) const {
551 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
552
553 switch (state_) {
554 case State::kIdle:
555 observer->OnIdle(idle_reason_);
556 break;
557 case State::kReporterRunning:
558 observer->OnReporterRunning();
559 break;
560 case State::kScanning:
561 observer->OnScanning();
562 break;
563 case State::kInfected:
564 observer->OnInfected(powered_by_partner_, scanner_results_);
565 break;
566 case State::kCleaning:
567 observer->OnCleaning(powered_by_partner_, scanner_results_);
568 break;
569 case State::kRebootRequired:
570 observer->OnRebootRequired();
571 break;
572 }
573 }
574
SetStateAndNotifyObservers(State state)575 void ChromeCleanerControllerImpl::SetStateAndNotifyObservers(State state) {
576 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
577 DCHECK_NE(state_, state);
578
579 state_ = state;
580
581 if (state_ == State::kIdle || state_ == State::kRebootRequired)
582 ResetCleanerDataAndInvalidateWeakPtrs();
583
584 for (auto& observer : observer_list_)
585 NotifyObserver(&observer);
586 }
587
ResetCleanerDataAndInvalidateWeakPtrs()588 void ChromeCleanerControllerImpl::ResetCleanerDataAndInvalidateWeakPtrs() {
589 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
590
591 weak_factory_.InvalidateWeakPtrs();
592 reporter_invocation_.reset();
593 prompt_user_reply_callback_.Reset();
594 }
595
OnChromeCleanerFetchedAndVerified(base::FilePath executable_path)596 void ChromeCleanerControllerImpl::OnChromeCleanerFetchedAndVerified(
597 base::FilePath executable_path) {
598 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
599 DCHECK_EQ(State::kScanning, state());
600 DCHECK(reporter_invocation_);
601
602 if (executable_path.empty()) {
603 idle_reason_ = IdleReason::kCleanerDownloadFailed;
604 SetStateAndNotifyObservers(State::kIdle);
605 RecordPromptNotShownWithReasonHistogram(
606 NO_PROMPT_REASON_CLEANER_DOWNLOAD_FAILED);
607 return;
608 }
609
610 DCHECK(executable_path.MatchesExtension(FILE_PATH_LITERAL(".exe")));
611
612 ChromeCleanerRunner::ChromeMetricsStatus metrics_status =
613 delegate_->IsMetricsAndCrashReportingEnabled()
614 ? ChromeCleanerRunner::ChromeMetricsStatus::kEnabled
615 : ChromeCleanerRunner::ChromeMetricsStatus::kDisabled;
616
617 ChromeCleanerRunner::RunChromeCleanerAndReplyWithExitCode(
618 extension_service_, extension_registry_, executable_path,
619 *reporter_invocation_, metrics_status,
620 base::BindOnce(&ChromeCleanerControllerImpl::WeakOnPromptUser,
621 weak_factory_.GetWeakPtr()),
622 base::BindOnce(&ChromeCleanerControllerImpl::OnConnectionClosed,
623 weak_factory_.GetWeakPtr()),
624 base::BindOnce(&ChromeCleanerControllerImpl::OnCleanerProcessDone,
625 weak_factory_.GetWeakPtr()),
626 // Our callbacks should be dispatched to the UI thread only.
627 base::ThreadTaskRunnerHandle::Get());
628
629 time_scanning_started_ = base::Time::Now();
630 }
631
632 // static
WeakOnPromptUser(const base::WeakPtr<ChromeCleanerControllerImpl> & controller,ChromeCleanerScannerResults && scanner_results,ChromePromptActions::PromptUserReplyCallback reply_callback)633 void ChromeCleanerControllerImpl::WeakOnPromptUser(
634 const base::WeakPtr<ChromeCleanerControllerImpl>& controller,
635 ChromeCleanerScannerResults&& scanner_results,
636 ChromePromptActions::PromptUserReplyCallback reply_callback) {
637 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
638
639 // If the weak pointer has been invalidated, the controller is no longer able
640 // to receive callbacks, so respond with DENIED immediately.
641 if (!controller) {
642 std::move(reply_callback).Run(PromptUserResponse::DENIED);
643 return;
644 }
645
646 controller->OnPromptUser(std::move(scanner_results),
647 std::move(reply_callback));
648 }
649
OnPromptUser(ChromeCleanerScannerResults && scanner_results,ChromePromptActions::PromptUserReplyCallback reply_callback)650 void ChromeCleanerControllerImpl::OnPromptUser(
651 ChromeCleanerScannerResults&& scanner_results,
652 ChromePromptActions::PromptUserReplyCallback reply_callback) {
653 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
654 DCHECK_EQ(State::kScanning, state());
655 DCHECK(scanner_results_.files_to_delete().empty());
656 DCHECK(scanner_results_.registry_keys().empty());
657 DCHECK(scanner_results_.extension_ids().empty());
658 DCHECK(!prompt_user_reply_callback_);
659 DCHECK(!time_scanning_started_.is_null());
660
661 UMA_HISTOGRAM_LONG_TIMES_100("SoftwareReporter.Cleaner.ScanningTime",
662 base::Time::Now() - time_scanning_started_);
663
664 if (scanner_results.files_to_delete().empty()) {
665 std::move(reply_callback).Run(PromptUserResponse::DENIED);
666 idle_reason_ = IdleReason::kScanningFoundNothing;
667 SetStateAndNotifyObservers(State::kIdle);
668 RecordPromptNotShownWithReasonHistogram(NO_PROMPT_REASON_NOTHING_FOUND);
669
670 // TODO(crbug.com/1139806): The scan completion timestamp is not always
671 // written to the registry. As a workaround, write the completion
672 // timestamp also to a pref. This ensures that the timestamp is preserved
673 // in case Chrome is still opened when the scan completes. Remove this
674 // workaround once the timestamp is written to the registry in all cases.
675 PrefService* pref_service = g_browser_process->local_state();
676 if (pref_service) {
677 pref_service->SetTime(prefs::kChromeCleanerScanCompletionTime,
678 base::Time::Now());
679 }
680
681 return;
682 }
683
684 UMA_HISTOGRAM_COUNTS_1000("SoftwareReporter.NumberOfFilesToDelete",
685 scanner_results.files_to_delete().size());
686 scanner_results_ = std::move(scanner_results);
687 prompt_user_reply_callback_ = std::move(reply_callback);
688 SetStateAndNotifyObservers(State::kInfected);
689 }
690
OnConnectionClosed()691 void ChromeCleanerControllerImpl::OnConnectionClosed() {
692 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
693 DCHECK_NE(State::kIdle, state());
694 DCHECK_NE(State::kRebootRequired, state());
695
696 if (state() == State::kScanning || state() == State::kInfected) {
697 if (state() == State::kScanning) {
698 RecordPromptNotShownWithReasonHistogram(
699 NO_PROMPT_REASON_IPC_CONNECTION_BROKEN);
700 RecordIPCDisconnectedHistogram(IPC_DISCONNECTED_LOST_WHILE_SCANNING);
701 } else {
702 RecordIPCDisconnectedHistogram(IPC_DISCONNECTED_LOST_USER_PROMPTED);
703 }
704
705 idle_reason_ = IdleReasonWhenConnectionClosedTooSoon(state());
706 SetStateAndNotifyObservers(State::kIdle);
707 return;
708 }
709 // Nothing to do if OnConnectionClosed() is called in other states:
710 // - This function will not be called in the kIdle and kRebootRequired
711 // states since we invalidate all weak pointers when we enter those states.
712 // - In the kCleaning state, we don't care about the connection to the Chrome
713 // Cleaner process since communication via IPC is complete and only the
714 // exit code of the process is of any use to us (for deciding whether we
715 // need to reboot).
716 RecordIPCDisconnectedHistogram(IPC_DISCONNECTED_SUCCESS);
717 }
718
OnCleanerProcessDone(ChromeCleanerRunner::ProcessStatus process_status)719 void ChromeCleanerControllerImpl::OnCleanerProcessDone(
720 ChromeCleanerRunner::ProcessStatus process_status) {
721 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
722
723 if (state() == State::kScanning || state() == State::kInfected) {
724 idle_reason_ = IdleReasonWhenConnectionClosedTooSoon(state());
725 SetStateAndNotifyObservers(State::kIdle);
726 return;
727 }
728
729 DCHECK_EQ(State::kCleaning, state());
730 DCHECK_NE(ChromeCleanerRunner::LaunchStatus::kLaunchFailed,
731 process_status.launch_status);
732
733 if (process_status.launch_status ==
734 ChromeCleanerRunner::LaunchStatus::kSuccess) {
735 if (process_status.exit_code == kRebootRequiredExitCode ||
736 process_status.exit_code == kRebootNotRequiredExitCode) {
737 DCHECK(!time_cleanup_started_.is_null());
738 UMA_HISTOGRAM_CUSTOM_TIMES("SoftwareReporter.Cleaner.CleaningTime",
739 base::Time::Now() - time_cleanup_started_,
740 base::TimeDelta::FromMilliseconds(1),
741 base::TimeDelta::FromHours(5), 100);
742 }
743
744 if (process_status.exit_code == kRebootRequiredExitCode) {
745 RecordCleanupResultHistogram(CLEANUP_RESULT_REBOOT_REQUIRED);
746 SetStateAndNotifyObservers(State::kRebootRequired);
747
748 // Start the reboot prompt flow.
749 delegate_->StartRebootPromptFlow(this);
750 return;
751 }
752
753 if (process_status.exit_code == kRebootNotRequiredExitCode) {
754 RecordCleanupResultHistogram(CLEANUP_RESULT_SUCCEEDED);
755 delegate_->ResetTaggedProfiles(
756 g_browser_process->profile_manager()->GetLoadedProfiles(),
757 base::DoNothing());
758 idle_reason_ = IdleReason::kCleaningSucceeded;
759 SetStateAndNotifyObservers(State::kIdle);
760 return;
761 }
762 }
763
764 RecordCleanupResultHistogram(CLEANUP_RESULT_FAILED);
765 idle_reason_ = IdleReason::kCleaningFailed;
766 SetStateAndNotifyObservers(State::kIdle);
767 }
768
InitiateReboot()769 void ChromeCleanerControllerImpl::InitiateReboot() {
770 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
771
772 installer::ScopedTokenPrivilege scoped_se_shutdown_privilege(
773 SE_SHUTDOWN_NAME);
774 if (!scoped_se_shutdown_privilege.is_enabled() ||
775 !::ExitWindowsEx(EWX_REBOOT, SHTDN_REASON_MAJOR_SOFTWARE |
776 SHTDN_REASON_MINOR_OTHER |
777 SHTDN_REASON_FLAG_PLANNED)) {
778 for (auto& observer : observer_list_)
779 observer.OnRebootFailed();
780 }
781 }
782
783 } // namespace safe_browsing
784