Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(959)

Side by Side Diff: content/browser/download/quarantine_win.cc

Issue 2124373002: [PPAPI] Quarantine files that are writeable by a Pepper plugin. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@consolidate-file-metadata
Patch Set: Address comments. Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 "content/browser/download/quarantine.h"
6
7 #include <windows.h>
8
9 #include <cguid.h>
10 #include <objbase.h>
11 #include <shellapi.h>
12 #include <shlobj.h>
13 #include <shobjidl.h>
14 #include <wininet.h>
15
16 #include "base/files/file_util.h"
17 #include "base/guid.h"
18 #include "base/logging.h"
19 #include "base/macros.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/metrics/sparse_histogram.h"
22 #include "base/strings/string_piece.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "base/win/scoped_comptr.h"
26 #include "base/win/scoped_handle.h"
27 #include "url/gurl.h"
28
29 namespace content {
30 namespace {
31
32 // [MS-FSCC] Section 5.6.1
33 const base::FilePath::CharType kZoneIdentifierStreamSuffix[] =
34 FILE_PATH_LITERAL(":Zone.Identifier");
35
36 // UMA enumeration for recording Download.AttachmentServicesResult.
37 enum class AttachmentServicesResult : int {
38 SUCCESS_WITH_MOTW = 0,
39 SUCCESS_WITHOUT_MOTW = 1,
40 SUCCESS_WITHOUT_FILE = 2,
41 NO_ATTACHMENT_SERVICES = 3,
42 FAILED_TO_SET_PARAMETER = 4,
43 BLOCKED_WITH_FILE = 5,
44 BLOCKED_WITHOUT_FILE = 6,
45 INFECTED_WITH_FILE = 7,
46 INFECTED_WITHOUT_FILE = 8,
47 ACCESS_DENIED_WITH_FILE = 9,
48 ACCESS_DENIED_WITHOUT_FILE = 10,
49 OTHER_WITH_FILE = 11,
50 OTHER_WITHOUT_FILE = 12,
51 };
52
53 void RecordAttachmentServicesResult(AttachmentServicesResult type) {
54 UMA_HISTOGRAM_SPARSE_SLOWLY("Download.AttachmentServices.Result",
55 static_cast<int>(type));
56 }
57
58 bool ZoneIdentifierPresentForFile(const base::FilePath& path) {
59 const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
60 base::FilePath::StringType zone_identifier_path =
61 path.value() + kZoneIdentifierStreamSuffix;
62 base::win::ScopedHandle file(
63 CreateFile(zone_identifier_path.c_str(), GENERIC_READ, kShare, nullptr,
64 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
65 if (!file.IsValid())
66 return false;
67
68 // The zone identifier contents is expected to be:
69 // "[ZoneTransfer]\r\nZoneId=3\r\n". The actual ZoneId can be different. A
70 // buffer of 16 bytes is sufficient for testing whether the contents start
71 // with "[ZoneTransfer]".
72 std::vector<char> zone_identifier_contents(16);
73 DWORD actual_length = 0;
74 if (!ReadFile(file.Get(), &zone_identifier_contents.front(),
75 zone_identifier_contents.size(), &actual_length, NULL))
76 return false;
77 base::StringPiece zone_identifier_string(&zone_identifier_contents.front(),
78 actual_length);
79 return zone_identifier_string.find("[ZoneTransfer]") == 0;
80 }
81
82 void RecordAttachmentServicesSaveResult(const base::FilePath& file,
83 HRESULT hr) {
84 bool file_exists = base::PathExists(file);
85 switch (hr) {
86 case INET_E_SECURITY_PROBLEM:
87 RecordAttachmentServicesResult(
88 file_exists ? AttachmentServicesResult::BLOCKED_WITH_FILE
89 : AttachmentServicesResult::BLOCKED_WITHOUT_FILE);
90 break;
91
92 case E_FAIL:
93 RecordAttachmentServicesResult(
94 file_exists ? AttachmentServicesResult::INFECTED_WITH_FILE
95 : AttachmentServicesResult::INFECTED_WITHOUT_FILE);
96 break;
97
98 case E_ACCESSDENIED:
99 case ERROR_ACCESS_DENIED:
100 // ERROR_ACCESS_DENIED is not a valid HRESULT. However,
101 // IAttachmentExecute::Save() is known to return it and other system error
102 // codes in practice.
103 RecordAttachmentServicesResult(
104 file_exists ? AttachmentServicesResult::ACCESS_DENIED_WITH_FILE
105 : AttachmentServicesResult::ACCESS_DENIED_WITHOUT_FILE);
106 break;
107
108 default:
109 if (SUCCEEDED(hr)) {
110 bool motw_exists = file_exists && ZoneIdentifierPresentForFile(file);
111 RecordAttachmentServicesResult(
112 file_exists
113 ? motw_exists ? AttachmentServicesResult::SUCCESS_WITH_MOTW
114 : AttachmentServicesResult::SUCCESS_WITHOUT_MOTW
115 : AttachmentServicesResult::SUCCESS_WITHOUT_FILE);
116 return;
117 }
118
119 // Failure codes.
120 RecordAttachmentServicesResult(
121 file_exists ? AttachmentServicesResult::OTHER_WITH_FILE
122 : AttachmentServicesResult::OTHER_WITHOUT_FILE);
123 }
124 }
125
126 // Sets the Zone Identifier on the file to "Internet" (3). Returns true if the
127 // function succeeds, false otherwise. A failure is expected if alternate
128 // streams are not supported, like a file on a FAT32 filesystem. This function
129 // does not invoke Windows Attachment Execution Services.
130 //
131 // |full_path| is the path to the downloaded file.
132 QuarantineFileResult SetInternetZoneIdentifierDirectly(
133 const base::FilePath& full_path) {
134 const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
135 std::wstring path = full_path.value() + kZoneIdentifierStreamSuffix;
136 HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, nullptr,
137 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
138 if (INVALID_HANDLE_VALUE == file)
139 return QuarantineFileResult::ANNOTATION_FAILED;
140
141 static const char kIdentifier[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
142 // Don't include trailing null in data written.
143 static const DWORD kIdentifierSize = arraysize(kIdentifier) - 1;
144 DWORD written = 0;
145 BOOL write_result =
146 WriteFile(file, kIdentifier, kIdentifierSize, &written, nullptr);
147 BOOL flush_result = FlushFileBuffers(file);
148 CloseHandle(file);
149
150 return write_result && flush_result && written == kIdentifierSize
151 ? QuarantineFileResult::OK
152 : QuarantineFileResult::ANNOTATION_FAILED;
153 }
154
155 // Invokes IAttachmentExecute::Save on CLSID_AttachmentServices to validate the
156 // downloaded file. The call may scan the file for viruses and if necessary,
157 // annotate it with evidence. As a result of the validation, the file may be
158 // deleted. See: http://msdn.microsoft.com/en-us/bb776299
159 //
160 // IAE::Save() will delete the file if it was found to be blocked by local
161 // security policy or if it was found to be infected. The call may also delete
162 // the file due to other failures (http://crbug.com/153212). A failure code will
163 // be returned in these cases.
164 //
165 // The return value is |false| iff the function fails to invoke
166 // IAttachmentExecute::Save(). If the function returns |true|, then the result
167 // of invoking IAttachmentExecute::Save() is stored in |save_result|.
168 //
169 // Typical |save_result| values:
170 // S_OK : The file was okay. If any viruses were found, they were cleaned.
171 // E_FAIL : Virus infected.
172 // INET_E_SECURITY_PROBLEM : The file was blocked due to security policy.
173 //
174 // Any other return value indicates an unexpected error during the scan.
175 //
176 // |full_path| : is the path to the downloaded file. This should be the final
177 // path of the download. Must be present.
178 // |source_url|: the source URL for the download. If empty, the source will
179 // be set to 'about:internet'.
180 // |referrer_url|: the referrer URL for the download. If empty, the referrer
181 // will not be set.
182 // |client_guid|: the GUID to be set in the IAttachmentExecute client slot.
183 // Used to identify the app to the system AV function.
184 // |save_result|: Receives the result of invoking IAttachmentExecute::Save().
185 bool InvokeAttachmentServices(const base::FilePath& full_path,
186 const std::string& source_url,
187 const std::string& referrer_url,
188 const GUID& client_guid,
189 HRESULT* save_result) {
190 base::win::ScopedComPtr<IAttachmentExecute> attachment_services;
191 HRESULT hr = attachment_services.CreateInstance(CLSID_AttachmentServices);
192 *save_result = S_OK;
193
194 if (FAILED(hr)) {
195 // The thread must have COM initialized.
196 DCHECK_NE(CO_E_NOTINITIALIZED, hr);
197 RecordAttachmentServicesResult(
198 AttachmentServicesResult::NO_ATTACHMENT_SERVICES);
199 return false;
200 }
201
202 // Note that it is mandatory to check the return values from here on out. If
203 // setting one of the parameters fails, it could leave the object in a state
204 // where the final Save() call will also fail.
205
206 hr = attachment_services->SetClientGuid(client_guid);
207 if (FAILED(hr)) {
208 RecordAttachmentServicesResult(
209 AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
210 return false;
211 }
212
213 hr = attachment_services->SetLocalPath(full_path.value().c_str());
214 if (FAILED(hr)) {
215 RecordAttachmentServicesResult(
216 AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
217 return false;
218 }
219
220 // The source URL could be empty if it was not a valid URL or was not HTTP/S.
221 // If so, user "about:internet" as a fallback URL. The latter is known to
222 // reliably map to the Internet zone.
223 //
224 // In addition, URLs that are longer than INTERNET_MAX_URL_LENGTH are also
225 // known to cause problems for URLMon. Hence also use "about:internet" in
226 // these cases. See http://crbug.com/601538.
227 hr = attachment_services->SetSource(
228 source_url.empty() || source_url.size() > INTERNET_MAX_URL_LENGTH
229 ? L"about:internet"
230 : base::UTF8ToWide(source_url).c_str());
231 if (FAILED(hr)) {
232 RecordAttachmentServicesResult(
233 AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
234 return false;
235 }
236
237 // Only set referrer if one is present and shorter than
238 // INTERNET_MAX_URL_LENGTH. Also, the source_url is authoritative for
239 // determining the relative danger of |full_path| so we don't consider it an
240 // error if we have to skip the |referrer_url|.
241 if (!referrer_url.empty() && referrer_url.size() < INTERNET_MAX_URL_LENGTH) {
242 hr = attachment_services->SetReferrer(
243 base::UTF8ToWide(referrer_url).c_str());
244 if (FAILED(hr)) {
245 RecordAttachmentServicesResult(
246 AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
247 return false;
248 }
249 }
250
251 {
252 // This method has been known to take longer than 10 seconds in some
253 // instances.
254 SCOPED_UMA_HISTOGRAM_LONG_TIMER("Download.AttachmentServices.Duration");
255 *save_result = attachment_services->Save();
256 }
257 RecordAttachmentServicesSaveResult(full_path, *save_result);
258 return true;
259 }
260
261 // Maps a return code from an unsuccessful IAttachmentExecute::Save() call to a
262 // QuarantineFileResult.
263 QuarantineFileResult FailedSaveResultToQuarantineResult(HRESULT result) {
264 switch (result) {
265 case INET_E_SECURITY_PROBLEM: // 0x800c000e
266 // This is returned if the download was blocked due to security
267 // restrictions. E.g. if the source URL was in the Restricted Sites zone
268 // and downloads are blocked on that zone, then the download would be
269 // deleted and this error code is returned.
270 return QuarantineFileResult::BLOCKED_BY_POLICY;
271
272 case E_FAIL: // 0x80004005
273 // Returned if an anti-virus product reports an infection in the
274 // downloaded file during IAE::Save().
275 return QuarantineFileResult::VIRUS_INFECTED;
276
277 default:
278 // Any other error that occurs during IAttachmentExecute::Save() likely
279 // indicates a problem with the security check, but not necessarily the
280 // download. This also includes cases where SUCCEEDED(result) is true. In
281 // the latter case we are likely dealing with a situation where the file
282 // is missing after a successful scan. See http://crbug.com/153212.
283 return QuarantineFileResult::SECURITY_CHECK_FAILED;
284 }
285 }
286
287 } // namespace
288
289 QuarantineFileResult QuarantineFile(const base::FilePath& file,
290 const GURL& source_url,
291 const GURL& referrer_url,
292 const std::string& client_guid) {
293 base::ThreadRestrictions::AssertIOAllowed();
294
295 int64_t file_size = 0;
296 if (!base::PathExists(file) || !base::GetFileSize(file, &file_size))
297 return QuarantineFileResult::FILE_MISSING;
298
299 std::string braces_guid = "{" + client_guid + "}";
300 GUID guid = GUID_NULL;
301 if (base::IsValidGUID(client_guid)) {
302 HRESULT hr = CLSIDFromString(base::UTF8ToUTF16(braces_guid).c_str(), &guid);
303 if (FAILED(hr))
304 guid = GUID_NULL;
305 }
306
307 if (file_size == 0 || IsEqualGUID(guid, GUID_NULL)) {
308 // Calling InvokeAttachmentServices on an empty file can result in the file
309 // being deleted. Also an anti-virus scan doesn't make a lot of sense to
310 // perform on an empty file.
311 return SetInternetZoneIdentifierDirectly(file);
312 }
313
314 HRESULT save_result = S_OK;
315 bool attachment_services_available = InvokeAttachmentServices(
316 file, source_url.spec(), referrer_url.spec(), guid, &save_result);
317 if (!attachment_services_available)
318 return SetInternetZoneIdentifierDirectly(file);
319
320 // If the download file is missing after the call, then treat this as an
321 // interrupted download.
322 //
323 // If InvokeAttachmentServices() failed, but the downloaded file is still
324 // around, then don't interrupt the download. Attachment Execution Services
325 // deletes the submitted file if the downloaded file is blocked by policy or
326 // if it was found to be infected.
327 //
328 // If the file is still there, then the error could be due to Windows
329 // Attachment Services not being available or some other error during the AES
330 // invocation. In either case, we don't surface the error to the user.
331 if (!base::PathExists(file))
332 return FailedSaveResultToQuarantineResult(save_result);
333 return QuarantineFileResult::OK;
334 }
335
336 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/download/quarantine_mac_unittest.mm ('k') | content/browser/download/quarantine_win_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698