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