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