| Index: content/browser/download/quarantine_win.cc
|
| diff --git a/content/browser/safe_util_win.cc b/content/browser/download/quarantine_win.cc
|
| similarity index 14%
|
| rename from content/browser/safe_util_win.cc
|
| rename to content/browser/download/quarantine_win.cc
|
| index 820aab5b468e73ab4fc723cd897c5e85e64ff2e9..e478ed3a480691dfd81f214a8bc963686217a930 100644
|
| --- a/content/browser/safe_util_win.cc
|
| +++ b/content/browser/download/quarantine_win.cc
|
| @@ -1,93 +1,288 @@
|
| -// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +#include "content/browser/download/quarantine.h"
|
| +
|
| +#include <windows.h>
|
| +
|
| +#include <cguid.h>
|
| +#include <objbase.h>
|
| +#include <shellapi.h>
|
| #include <shlobj.h>
|
| #include <shobjidl.h>
|
|
|
| -#include "content/browser/safe_util_win.h"
|
| -
|
| -#include "base/files/file_path.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/guid.h"
|
| #include "base/logging.h"
|
| #include "base/macros.h"
|
| -#include "base/strings/string_util.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| +#include "base/metrics/sparse_histogram.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| +#include "base/threading/thread_restrictions.h"
|
| #include "base/win/scoped_comptr.h"
|
| -#include "ui/base/win/shell.h"
|
| +#include "base/win/scoped_handle.h"
|
| #include "url/gurl.h"
|
|
|
| namespace content {
|
| namespace {
|
|
|
| +const base::FilePath::CharType kZoneIdentifierStreamSuffix[] =
|
| + FILE_PATH_LITERAL(":Zone.Identifier");
|
| +
|
| +// UMA enumeration for recording Download.AttachmentServicesResult.
|
| +enum class AttachmentServicesResult : int {
|
| + SUCCESS_WITH_MOTW = 0,
|
| + SUCCESS_WITHOUT_MOTW = 1,
|
| + SUCCESS_WITHOUT_FILE = 2,
|
| + NO_ATTACHMENT_SERVICES = 3,
|
| + FAILED_TO_SET_PARAMETER = 4,
|
| + BLOCKED_WITH_FILE = 5,
|
| + BLOCKED_WITHOUT_FILE = 6,
|
| + INFECTED_WITH_FILE = 7,
|
| + INFECTED_WITHOUT_FILE = 8,
|
| + ACCESS_DENIED_WITH_FILE = 9,
|
| + ACCESS_DENIED_WITHOUT_FILE = 10,
|
| + OTHER_WITH_FILE = 11,
|
| + OTHER_WITHOUT_FILE = 12,
|
| +};
|
| +
|
| +void RecordAttachmentServicesResult(AttachmentServicesResult type) {
|
| + UMA_HISTOGRAM_SPARSE_SLOWLY("Download.AttachmentServices.Result",
|
| + static_cast<int>(type));
|
| +}
|
| +
|
| +bool ZoneIdentifierPresentForFile(const base::FilePath& path) {
|
| + const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
| + base::FilePath::StringType zone_identifier_path =
|
| + path.value() + kZoneIdentifierStreamSuffix;
|
| + base::win::ScopedHandle file(
|
| + CreateFile(zone_identifier_path.c_str(), GENERIC_READ, kShare, nullptr,
|
| + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
|
| + return file.IsValid();
|
| +}
|
| +
|
| +void RecordAttachmentServicesSaveResult(const base::FilePath& file,
|
| + HRESULT hr) {
|
| + bool file_exists = base::PathExists(file);
|
| + if (SUCCEEDED(hr)) {
|
| + bool motw_exists = file_exists && ZoneIdentifierPresentForFile(file);
|
| + RecordAttachmentServicesResult(
|
| + file_exists
|
| + ? motw_exists ? AttachmentServicesResult::SUCCESS_WITH_MOTW
|
| + : AttachmentServicesResult::SUCCESS_WITHOUT_MOTW
|
| + : AttachmentServicesResult::SUCCESS_WITHOUT_FILE);
|
| + return;
|
| + }
|
| +
|
| + switch (hr) {
|
| + case INET_E_SECURITY_PROBLEM:
|
| + RecordAttachmentServicesResult(
|
| + file_exists ? AttachmentServicesResult::BLOCKED_WITH_FILE
|
| + : AttachmentServicesResult::BLOCKED_WITHOUT_FILE);
|
| + break;
|
| +
|
| + case E_FAIL:
|
| + RecordAttachmentServicesResult(
|
| + file_exists ? AttachmentServicesResult::INFECTED_WITH_FILE
|
| + : AttachmentServicesResult::INFECTED_WITHOUT_FILE);
|
| + break;
|
| +
|
| + case E_ACCESSDENIED:
|
| + case ERROR_ACCESS_DENIED:
|
| + RecordAttachmentServicesResult(
|
| + file_exists ? AttachmentServicesResult::ACCESS_DENIED_WITH_FILE
|
| + : AttachmentServicesResult::ACCESS_DENIED_WITHOUT_FILE);
|
| + break;
|
| +
|
| + default:
|
| + RecordAttachmentServicesResult(
|
| + file_exists ? AttachmentServicesResult::OTHER_WITH_FILE
|
| + : AttachmentServicesResult::OTHER_WITHOUT_FILE);
|
| + }
|
| +}
|
| +
|
| // Sets the Zone Identifier on the file to "Internet" (3). Returns true if the
|
| -// function succeeds, false otherwise. A failure is expected on system where
|
| -// the Zone Identifier is not supported, like a machine with a FAT32 filesystem.
|
| -// This function does not invoke Windows Attachment Execution Services.
|
| +// function succeeds, false otherwise. A failure is expected if alternate
|
| +// streams are not supported, like a file on a FAT32 filesystem. This function
|
| +// does not invoke Windows Attachment Execution Services.
|
| //
|
| // |full_path| is the path to the downloaded file.
|
| -bool SetInternetZoneIdentifierDirectly(const base::FilePath& full_path) {
|
| +QuarantineFileResult SetInternetZoneIdentifierDirectly(
|
| + const base::FilePath& full_path) {
|
| const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
| - std::wstring path = full_path.value() + L":Zone.Identifier";
|
| - HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, NULL,
|
| - OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
| + std::wstring path = full_path.value() + kZoneIdentifierStreamSuffix;
|
| + HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, nullptr,
|
| + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
| if (INVALID_HANDLE_VALUE == file)
|
| - return false;
|
| + return QuarantineFileResult::ANNOTATION_FAILED;
|
|
|
| static const char kIdentifier[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
|
| // Don't include trailing null in data written.
|
| static const DWORD kIdentifierSize = arraysize(kIdentifier) - 1;
|
| DWORD written = 0;
|
| - BOOL result = WriteFile(file, kIdentifier, kIdentifierSize, &written, NULL);
|
| + BOOL write_result =
|
| + WriteFile(file, kIdentifier, kIdentifierSize, &written, nullptr);
|
| BOOL flush_result = FlushFileBuffers(file);
|
| CloseHandle(file);
|
|
|
| - if (!result || !flush_result || written != kIdentifierSize) {
|
| - NOTREACHED();
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| + return write_result && flush_result && written == kIdentifierSize
|
| + ? QuarantineFileResult::OK
|
| + : QuarantineFileResult::ANNOTATION_FAILED;
|
| }
|
|
|
| -} // namespace
|
| -
|
| -HRESULT AVScanFile(const base::FilePath& full_path,
|
| - const std::string& source_url,
|
| - const GUID& client_guid) {
|
| +// Invokes IAttachmentExecute::Save on CLSID_AttachmentServices to validate the
|
| +// downloaded file. The call may scan the file for viruses and if necessary,
|
| +// annotate it with evidence. As a result of the validation, the file may be
|
| +// deleted. See: http://msdn.microsoft.com/en-us/bb776299
|
| +//
|
| +// IAE::Save() will delete the file if it was found to be blocked by local
|
| +// security policy or if it was found to be infected. The call may also delete
|
| +// the file due to other failures (http://crbug.com/153212). A failure code will
|
| +// be returned in these cases.
|
| +//
|
| +// The return value is |false| iff the function fails to invoke
|
| +// IAttachmentExecute::Save(). If the function returns |true|, then the result
|
| +// of invoking IAttachmentExecute::Save() is stored in |save_result|.
|
| +//
|
| +// Typical |save_result| values:
|
| +// S_OK : The file was okay. If any viruses were found, they were cleaned.
|
| +// E_FAIL : Virus infected.
|
| +// INET_E_SECURITY_PROBLEM : The file was blocked due to security policy.
|
| +//
|
| +// Any other return value indicates an unexpected error during the scan.
|
| +//
|
| +// |full_path| : is the path to the downloaded file. This should be the final
|
| +// path of the download. Must be present.
|
| +// |source_url|: the source URL for the download. If empty, the source will
|
| +// not be set.
|
| +// |client_guid|: the GUID to be set in the IAttachmentExecute client slot.
|
| +// Used to identify the app to the system AV function.
|
| +// If GUID_NULL is passed, no client GUID is set.
|
| +// |save_result|: Receives the result of invoking IAttachmentExecute::Save().
|
| +bool InvokeAttachmentServices(const base::FilePath& full_path,
|
| + const std::string& source_url,
|
| + const GUID& client_guid,
|
| + HRESULT* save_result) {
|
| base::win::ScopedComPtr<IAttachmentExecute> attachment_services;
|
| HRESULT hr = attachment_services.CreateInstance(CLSID_AttachmentServices);
|
| + *save_result = S_OK;
|
|
|
| if (FAILED(hr)) {
|
| // The thread must have COM initialized.
|
| DCHECK_NE(CO_E_NOTINITIALIZED, hr);
|
| -
|
| - // We don't have Attachment Execution Services, it must be a pre-XP.SP2
|
| - // Windows installation, or the thread does not have COM initialized. Try to
|
| - // set the zone information directly. Failure is not considered an error.
|
| - SetInternetZoneIdentifierDirectly(full_path);
|
| - return hr;
|
| + RecordAttachmentServicesResult(
|
| + AttachmentServicesResult::NO_ATTACHMENT_SERVICES);
|
| + return false;
|
| }
|
|
|
| - if (!IsEqualGUID(client_guid, GUID_NULL)) {
|
| - hr = attachment_services->SetClientGuid(client_guid);
|
| - if (FAILED(hr))
|
| - return hr;
|
| + hr = attachment_services->SetClientGuid(client_guid);
|
| + if (FAILED(hr)) {
|
| + RecordAttachmentServicesResult(
|
| + AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
|
| + return false;
|
| }
|
|
|
| hr = attachment_services->SetLocalPath(full_path.value().c_str());
|
| - if (FAILED(hr))
|
| - return hr;
|
| + if (FAILED(hr)) {
|
| + RecordAttachmentServicesResult(
|
| + AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
|
| + return false;
|
| + }
|
|
|
| // Note: SetSource looks like it needs to be called, even if empty.
|
| // Docs say it is optional, but it appears not calling it at all sets
|
| // a zone that is too restrictive.
|
| hr = attachment_services->SetSource(base::UTF8ToWide(source_url).c_str());
|
| - if (FAILED(hr))
|
| - return hr;
|
| + if (FAILED(hr)) {
|
| + RecordAttachmentServicesResult(
|
| + AttachmentServicesResult::FAILED_TO_SET_PARAMETER);
|
| + return false;
|
| + }
|
| +
|
| + {
|
| + // This method has been known to take longer than 10 seconds in some
|
| + // instances.
|
| + SCOPED_UMA_HISTOGRAM_LONG_TIMER("Download.AttachmentServices.Duration");
|
| + *save_result = attachment_services->Save();
|
| + }
|
| + RecordAttachmentServicesSaveResult(full_path, *save_result);
|
| + return true;
|
| +}
|
| +
|
| +// Maps a return code from an unsuccessful IAttachmentExecute::Save() call to a
|
| +// QuarantineFileResult.
|
| +QuarantineFileResult FailedSaveResultToQuarantineResult(HRESULT result) {
|
| + switch (result) {
|
| + case INET_E_SECURITY_PROBLEM: // 0x800c000e
|
| + // This is returned if the download was blocked due to security
|
| + // restrictions. E.g. if the source URL was in the Restricted Sites zone
|
| + // and downloads are blocked on that zone, then the download would be
|
| + // deleted and this error code is returned.
|
| + return QuarantineFileResult::BLOCKED_BY_POLICY;
|
| +
|
| + case E_FAIL: // 0x80004005
|
| + // Returned if an anti-virus product reports an infection in the
|
| + // downloaded file during IAE::Save().
|
| + return QuarantineFileResult::VIRUS_INFECTED;
|
| +
|
| + default:
|
| + // Any other error that occurs during IAttachmentExecute::Save() likely
|
| + // indicates a problem with the security check, but not necessarily the
|
| + // download. This also includes cases where SUCCEEDED(result) is true. In
|
| + // the latter case we are likely dealing with a situation where the file
|
| + // is missing after a successful scan. See http://crbug.com/153212.
|
| + return QuarantineFileResult::SECURITY_CHECK_FAILED;
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +QuarantineFileResult QuarantineFile(const base::FilePath& file,
|
| + const GURL& source_url,
|
| + const GURL& referrer_url,
|
| + const std::string& client_guid) {
|
| + base::ThreadRestrictions::AssertIOAllowed();
|
| +
|
| + int64_t file_size = 0;
|
| + if (!base::PathExists(file) || !base::GetFileSize(file, &file_size))
|
| + return QuarantineFileResult::FILE_MISSING;
|
| +
|
| + std::string braces_guid = "{" + client_guid + "}";
|
| + GUID guid = GUID_NULL;
|
| + if (base::IsValidGUID(client_guid)) {
|
| + HRESULT hr = CLSIDFromString(base::UTF8ToUTF16(braces_guid).c_str(), &guid);
|
| + if (FAILED(hr))
|
| + guid = GUID_NULL;
|
| + }
|
| +
|
| + if (file_size == 0 || IsEqualGUID(guid, GUID_NULL)) {
|
| + // Calling InvokeAttachmentServices on an empty file can result in the file
|
| + // being deleted. Also an anti-virus scan doesn't make a lot of sense to
|
| + // perform on an empty file.
|
| + return SetInternetZoneIdentifierDirectly(file);
|
| + }
|
| +
|
| + HRESULT save_result = S_OK;
|
| + bool attachment_services_available =
|
| + InvokeAttachmentServices(file, source_url.spec(), guid, &save_result);
|
| + if (!attachment_services_available)
|
| + return SetInternetZoneIdentifierDirectly(file);
|
|
|
| - // A failure in the Save() call below could result in the downloaded file
|
| - // being deleted.
|
| - return attachment_services->Save();
|
| + // If the download file is missing after the call, then treat this as an
|
| + // interrupted download.
|
| + //
|
| + // If InvokeAttachmentServices() failed, but the downloaded file is still
|
| + // around, then don't interrupt the download. Attachment Execution Services
|
| + // deletes the submitted file if the downloaded file is blocked by policy or
|
| + // if it was found to be infected.
|
| + //
|
| + // If the file is still there, then the error could be due to Windows
|
| + // Attachment Services not being available or some other error during the AES
|
| + // invocation. In either case, we don't surface the error to the user.
|
| + if (!base::PathExists(file))
|
| + return FailedSaveResultToQuarantineResult(save_result);
|
| + return QuarantineFileResult::OK;
|
| }
|
|
|
| } // namespace content
|
|
|