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

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

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

Powered by Google App Engine
This is Rietveld 408576698