1 // Copyright 2019 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/chrome_cleaner/engines/target/test_engine_delegate.h"
6 
7 #include <windows.h>
8 
9 #include <map>
10 #include <string>
11 #include <utility>
12 #include <vector>
13 
14 #include "base/bind.h"
15 #include "base/files/file_path.h"
16 #include "base/logging.h"
17 #include "base/memory/scoped_refptr.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_piece.h"
20 #include "base/task_runner_util.h"
21 #include "base/threading/sequenced_task_runner_handle.h"
22 #include "base/win/scoped_handle.h"
23 #include "chrome/chrome_cleaner/constants/uws_id.h"
24 #include "chrome/chrome_cleaner/engines/common/engine_result_codes.h"
25 #include "chrome/chrome_cleaner/engines/target/engine_file_requests_proxy.h"
26 #include "chrome/chrome_cleaner/engines/target/engine_scan_results_proxy.h"
27 #include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
28 #include "chrome/chrome_cleaner/pup_data/pup_data.h"
29 #include "chrome/chrome_cleaner/pup_data/test_uws.h"
30 
31 namespace chrome_cleaner {
32 
33 namespace {
34 
35 // Map of test UwS id to the corresponding file content to be matched.
36 // This is leaked to avoid the order-of-destruction problem of global statics.
37 static const auto& kTestUwSFileContent = *new std::map<UwSId, std::string>{
38     {chrome_cleaner::kGoogleTestAUwSID, chrome_cleaner::kTestUwsAFileContents},
39     {chrome_cleaner::kGoogleTestBUwSID, chrome_cleaner::kTestUwsBFileContents}};
40 
41 // Scans for the UwS with id |uws_id| in a subfolder named "Startup" of the
42 // given |base_folder|. Returns an EngineResultCode. On success, sets
43 // |found_uws| to true if any UwS files were found, and also adds the file data
44 // to |pup| if it is not null.
ScanStartupSubfolderForUwSWithId(const base::FilePath & base_folder,UwSId uws_id,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,bool * found_uws,PUPData::PUP * pup)45 uint32_t ScanStartupSubfolderForUwSWithId(
46     const base::FilePath& base_folder,
47     UwSId uws_id,
48     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
49     bool* found_uws,
50     PUPData::PUP* pup) {
51   DCHECK(found_uws);
52 
53   const auto it = kTestUwSFileContent.find(uws_id);
54   if (it == kTestUwSFileContent.end()) {
55     // This stub implementation should only consider test UwS. No error, we
56     // just don't find anything.
57     return EngineResultCode::kSuccess;
58   }
59   const std::string uws_contents = it->second;
60 
61   base::FilePath folder = base_folder.Append(L"Startup");
62   base::FilePath file_pattern = folder.Append(L"*.exe");
63 
64   WIN32_FIND_DATAW file_info;
65   FindFileHandle find_handle;
66   uint32_t status = privileged_file_calls->FindFirstFile(
67       file_pattern, &file_info, &find_handle);
68   if (status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND) {
69     // Nothing found; no need to continue.
70     return EngineResultCode::kSuccess;
71   } else if (status != 0) {
72     LOG(ERROR) << "FindFirstFile failed with status " << status;
73     return EngineResultCode::kEngineInternal;
74   }
75 
76   for (; status == 0;
77        status = privileged_file_calls->FindNextFile(find_handle, &file_info)) {
78     base::FilePath file_path = folder.Append(file_info.cFileName);
79     base::win::ScopedHandle file_handle =
80         privileged_file_calls->OpenReadOnlyFile(file_path, 0);
81     if (!file_handle.IsValid()) {
82       PLOG(ERROR) << "Failed to open file";
83       continue;
84     }
85 
86     std::vector<char> contents(file_info.nFileSizeLow);
87     DWORD bytes_read = 0;
88     if (!::ReadFile(file_handle.Get(), contents.data(), contents.size(),
89                     &bytes_read, nullptr)) {
90       PLOG(ERROR) << "Failed to read file";
91       continue;
92     }
93 
94     if (base::StringPiece(contents.data(), bytes_read) == uws_contents) {
95       *found_uws = true;
96       if (pup) {
97         pup->AddDiskFootprint(file_path);
98         pup->AddDiskFootprintTraceLocation(file_path,
99                                            UwS_TraceLocation_FOUND_IN_SHELL);
100       }
101     }
102   }
103   LOG_IF(ERROR, status != ERROR_NO_MORE_FILES)
104       << "find_next_file failed with status " << status;
105 
106   status = privileged_file_calls->FindClose(find_handle);
107   LOG_IF(ERROR, status) << "find_close failed with status " << status;
108   return EngineResultCode::kSuccess;
109 }
110 
111 // Scans all supported locations for the UwS with id |uws_id|. Returns an
112 // EngineResultCode. On success, sets |found_uws| to true if any UwS files were
113 // found, and also adds the file data to |pup| if it is not null.
ScanForUwSWithId(UwSId uws_id,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,bool * found_uws,PUPData::PUP * pup)114 uint32_t ScanForUwSWithId(
115     UwSId uws_id,
116     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
117     bool* found_uws,
118     PUPData::PUP* pup) {
119   DCHECK(found_uws);
120 
121   // We can't retrieve the actual StartUp folder in the sandbox, and we won't
122   // have access to find the current username either. So scan all users using
123   // a hardcoded Start Menu location.
124   // TODO(joenotcharles): The actual folder path may vary based on Windows
125   // version and locale. We should find some way to get the correct system
126   // Start Menu path even in the sandbox.
127   base::FilePath base_folder = base::FilePath(L"C:\\Users");
128   base::FilePath file_pattern = base_folder.Append(L"*");
129 
130   WIN32_FIND_DATAW file_info;
131   FindFileHandle find_handle = 0;
132   uint32_t status = privileged_file_calls->FindFirstFile(
133       file_pattern, &file_info, &find_handle);
134   if (status) {
135     LOG_IF(ERROR,
136            status != ERROR_FILE_NOT_FOUND && status != ERROR_PATH_NOT_FOUND)
137         << "FindFirstFile failed with status " << status;
138     return EngineResultCode::kEngineInternal;
139   }
140 
141   for (; status == 0;
142        status = privileged_file_calls->FindNextFile(find_handle, &file_info)) {
143     if (!(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
144       continue;
145 
146     // Each subdirectory under Users is a user name.
147     std::wstring user_name(file_info.cFileName);
148     if (user_name == L"." || user_name == L"..")
149       continue;
150     base::FilePath folder = base_folder.Append(user_name)
151                                 .Append(L"AppData")
152                                 .Append(L"Roaming")
153                                 .Append(L"Microsoft")
154                                 .Append(L"Windows")
155                                 .Append(L"Start Menu")
156                                 .Append(L"Programs");
157     uint32_t result = ScanStartupSubfolderForUwSWithId(
158         folder, uws_id, privileged_file_calls, found_uws, pup);
159     if (result != EngineResultCode::kSuccess)
160       return result;
161   }
162 
163   return EngineResultCode::kSuccess;
164 }
165 
166 // Scans all supported locations that are enabled for all enabled UwS. Returns
167 // an EngineResultCode. On success, uses |report_result_calls| to report the
168 // UwSId's of all detected UwS. If |include_details| is true the reports will
169 // also include file details.
ScanForUwS(const std::vector<UwSId> & enabled_uws,const std::vector<UwS::TraceLocation> & enabled_trace_locations,bool include_details,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,scoped_refptr<EngineScanResultsProxy> report_result_calls)170 uint32_t ScanForUwS(
171     const std::vector<UwSId>& enabled_uws,
172     const std::vector<UwS::TraceLocation>& enabled_trace_locations,
173     bool include_details,
174     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
175     scoped_refptr<EngineScanResultsProxy> report_result_calls) {
176   // Only check the Startup folder, assuming it's enabled
177   if (!base::Contains(enabled_trace_locations,
178                       UwS_TraceLocation_FOUND_IN_SHELL)) {
179     return EngineResultCode::kSuccess;
180   }
181 
182   for (const UwSId uws_id : enabled_uws) {
183     PUPData::PUP pup;
184     bool found_uws = false;
185     uint32_t result =
186         ScanForUwSWithId(uws_id, privileged_file_calls, &found_uws,
187                          include_details ? &pup : nullptr);
188     if (result != EngineResultCode::kSuccess)
189       return result;
190     if (found_uws)
191       report_result_calls->FoundUwS(uws_id, pup);
192   }
193 
194   return EngineResultCode::kSuccess;
195 }
196 
CleanUwS(const std::vector<UwSId> & enabled_uws,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,scoped_refptr<CleanerEngineRequestsProxy> privileged_removal_calls)197 uint32_t CleanUwS(
198     const std::vector<UwSId>& enabled_uws,
199     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
200     scoped_refptr<CleanerEngineRequestsProxy> privileged_removal_calls) {
201   for (const UwSId uws_id : enabled_uws) {
202     PUPData::PUP pup;
203     bool found_uws = false;
204     uint32_t result =
205         ScanForUwSWithId(uws_id, privileged_file_calls, &found_uws, &pup);
206     if (result != EngineResultCode::kSuccess)
207       return result;
208     if (found_uws) {
209       for (const base::FilePath& file :
210            pup.expanded_disk_footprints.file_paths()) {
211         // Fall back to a post-reboot deletion if the delete fails.
212         if (!privileged_removal_calls->DeleteFile(file) &&
213             !privileged_removal_calls->DeleteFilePostReboot(file)) {
214           return EngineResultCode::kCleaningFailed;
215         }
216       }
217     }
218   }
219   return EngineResultCode::kSuccess;
220 }
221 
ScanDone(scoped_refptr<EngineFileRequestsProxy>,scoped_refptr<EngineRequestsProxy>,scoped_refptr<EngineScanResultsProxy> report_result_calls,uint32_t result)222 void ScanDone(scoped_refptr<EngineFileRequestsProxy> /*privileged_file_calls*/,
223               scoped_refptr<EngineRequestsProxy> /*privileged_scan_calls*/,
224               scoped_refptr<EngineScanResultsProxy> report_result_calls,
225               uint32_t result) {
226   report_result_calls->ScanDone(result);
227   // All proxies now go out of scope and can be deleted.
228 }
229 
CleanupDone(scoped_refptr<EngineFileRequestsProxy>,scoped_refptr<EngineRequestsProxy>,scoped_refptr<CleanerEngineRequestsProxy>,scoped_refptr<EngineCleanupResultsProxy> report_result_calls,uint32_t result)230 void CleanupDone(
231     scoped_refptr<EngineFileRequestsProxy> /*privileged_file_calls*/,
232     scoped_refptr<EngineRequestsProxy> /*privileged_scan_calls*/,
233     scoped_refptr<CleanerEngineRequestsProxy> /*privileged_removal_calls*/,
234     scoped_refptr<EngineCleanupResultsProxy> report_result_calls,
235     uint32_t result) {
236   report_result_calls->CleanupDone(result);
237   // All proxies now go out of scope and can be deleted.
238 }
239 
240 }  // namespace
241 
242 TestEngineDelegate::TestEngineDelegate() = default;
243 
244 TestEngineDelegate::~TestEngineDelegate() = default;
245 
engine() const246 Engine::Name TestEngineDelegate::engine() const {
247   return Engine::TEST_ONLY;
248 }
249 
Initialize(const base::FilePath & log_directory_path,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,mojom::EngineCommands::InitializeCallback done_callback)250 void TestEngineDelegate::Initialize(
251     const base::FilePath& log_directory_path,
252     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
253     mojom::EngineCommands::InitializeCallback done_callback) {
254   DCHECK(!work_thread_);
255   work_thread_ = std::make_unique<base::Thread>("TestEngineDelegate");
256   uint32_t result = work_thread_->Start() ? EngineResultCode::kSuccess
257                                           : EngineResultCode::kEngineInternal;
258   base::SequencedTaskRunnerHandle::Get()->PostTask(
259       FROM_HERE, base::BindOnce(std::move(done_callback), result));
260 }
261 
StartScan(const std::vector<UwSId> & enabled_uws,const std::vector<UwS::TraceLocation> & enabled_trace_locations,bool include_details,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,scoped_refptr<EngineRequestsProxy> privileged_scan_calls,scoped_refptr<EngineScanResultsProxy> report_result_calls)262 uint32_t TestEngineDelegate::StartScan(
263     const std::vector<UwSId>& enabled_uws,
264     const std::vector<UwS::TraceLocation>& enabled_trace_locations,
265     bool include_details,
266     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
267     scoped_refptr<EngineRequestsProxy> privileged_scan_calls,
268     scoped_refptr<EngineScanResultsProxy> report_result_calls) {
269   DCHECK(work_thread_);
270   base::PostTaskAndReplyWithResult(
271       work_thread_->task_runner().get(), FROM_HERE,
272       base::BindOnce(&ScanForUwS, enabled_uws, enabled_trace_locations,
273                      include_details, privileged_file_calls,
274                      report_result_calls),
275       base::BindOnce(&ScanDone, privileged_file_calls, privileged_scan_calls,
276                      report_result_calls));
277   return EngineResultCode::kSuccess;
278 }
279 
StartCleanup(const std::vector<UwSId> & enabled_uws,scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,scoped_refptr<EngineRequestsProxy> privileged_scan_calls,scoped_refptr<CleanerEngineRequestsProxy> privileged_removal_calls,scoped_refptr<EngineCleanupResultsProxy> report_result_calls)280 uint32_t TestEngineDelegate::StartCleanup(
281     const std::vector<UwSId>& enabled_uws,
282     scoped_refptr<EngineFileRequestsProxy> privileged_file_calls,
283     scoped_refptr<EngineRequestsProxy> privileged_scan_calls,
284     scoped_refptr<CleanerEngineRequestsProxy> privileged_removal_calls,
285     scoped_refptr<EngineCleanupResultsProxy> report_result_calls) {
286   DCHECK(work_thread_);
287   base::PostTaskAndReplyWithResult(
288       work_thread_->task_runner().get(), FROM_HERE,
289       base::BindOnce(&CleanUwS, enabled_uws, privileged_file_calls,
290                      privileged_removal_calls),
291       base::BindOnce(&CleanupDone, privileged_file_calls, privileged_scan_calls,
292                      privileged_removal_calls, report_result_calls));
293   return EngineResultCode::kSuccess;
294 }
295 
Finalize()296 uint32_t TestEngineDelegate::Finalize() {
297   work_thread_.reset();
298   return EngineResultCode::kSuccess;
299 }
300 
301 }  // namespace chrome_cleaner
302