Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 |
| OLD | NEW |