blob: 42d5adaa61ed61b99522600ddbaf19c765a02255 [file] [log] [blame]
// Copyright (c) 2019 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 "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include <algorithm>
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
#include "components/safe_browsing/core/proto/webprotect.pb.h"
namespace safe_browsing {
namespace {
constexpr int kMinBytesPerSecond = 1;
constexpr int kMaxBytesPerSecond = 100 * 1024 * 1024; // 100 MB/s
// TODO(drubery): This function would be simpler if the ClientDownloadResponse
// and MalwareDeepScanningVerdict used the same enum.
std::string MalwareVerdictToThreatType(
MalwareDeepScanningVerdict::Verdict verdict) {
switch (verdict) {
case MalwareDeepScanningVerdict::CLEAN:
return "SAFE";
case MalwareDeepScanningVerdict::UWS:
return "POTENTIALLY_UNWANTED";
case MalwareDeepScanningVerdict::MALWARE:
return "DANGEROUS";
case MalwareDeepScanningVerdict::VERDICT_UNSPECIFIED:
default:
return "UNKNOWN";
}
}
} // namespace
void MaybeReportDeepScanningVerdict(Profile* profile,
const GURL& url,
const std::string& file_name,
const std::string& download_digest_sha256,
const std::string& mime_type,
const std::string& trigger,
const int64_t content_size,
BinaryUploadService::Result result,
DeepScanningClientResponse response) {
DCHECK(std::all_of(download_digest_sha256.begin(),
download_digest_sha256.end(), [](const char& c) {
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}));
std::string unscanned_reason;
switch (result) {
case BinaryUploadService::Result::SUCCESS:
case BinaryUploadService::Result::UNAUTHORIZED:
// Don't report an unscanned file event on these results.
break;
case BinaryUploadService::Result::FILE_TOO_LARGE:
unscanned_reason = "fileTooLarge";
break;
case BinaryUploadService::Result::TIMEOUT:
unscanned_reason = "scanTimedOut";
break;
case BinaryUploadService::Result::FILE_ENCRYPTED:
unscanned_reason = "filePasswordProtected";
break;
case BinaryUploadService::Result::UNKNOWN:
unscanned_reason = "unknownError";
break;
case BinaryUploadService::Result::UPLOAD_FAILURE:
unscanned_reason = "uploadFailure";
break;
case BinaryUploadService::Result::FAILED_TO_GET_TOKEN:
unscanned_reason = "failedToGetToken";
break;
case BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE:
unscanned_reason = "unsupportedFileType";
}
if (!unscanned_reason.empty()) {
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
mime_type, trigger, unscanned_reason,
content_size);
}
if (result != BinaryUploadService::Result::SUCCESS)
return;
if (response.has_malware_scan_verdict() &&
response.malware_scan_verdict().verdict() ==
MalwareDeepScanningVerdict::SCAN_FAILURE) {
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
mime_type, trigger, "malwareScanFailed",
content_size);
}
if (response.has_dlp_scan_verdict() &&
response.dlp_scan_verdict().status() != DlpDeepScanningVerdict::SUCCESS) {
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnUnscannedFileEvent(url, file_name, download_digest_sha256,
mime_type, trigger, "dlpScanFailed",
content_size);
}
if (response.malware_scan_verdict().verdict() ==
MalwareDeepScanningVerdict::UWS ||
response.malware_scan_verdict().verdict() ==
MalwareDeepScanningVerdict::MALWARE) {
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnDangerousDeepScanningResult(
url, file_name, download_digest_sha256,
MalwareVerdictToThreatType(
response.malware_scan_verdict().verdict()),
mime_type, trigger, content_size);
}
if (response.dlp_scan_verdict().status() == DlpDeepScanningVerdict::SUCCESS) {
if (!response.dlp_scan_verdict().triggered_rules().empty()) {
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnSensitiveDataEvent(response.dlp_scan_verdict(), url, file_name,
download_digest_sha256, mime_type, trigger,
content_size);
}
}
}
std::string DeepScanAccessPointToString(DeepScanAccessPoint access_point) {
switch (access_point) {
case DeepScanAccessPoint::DOWNLOAD:
return "Download";
case DeepScanAccessPoint::UPLOAD:
return "Upload";
case DeepScanAccessPoint::DRAG_AND_DROP:
return "DragAndDrop";
case DeepScanAccessPoint::PASTE:
return "Paste";
}
NOTREACHED();
return "";
}
void RecordDeepScanMetrics(DeepScanAccessPoint access_point,
base::TimeDelta duration,
int64_t total_bytes,
const BinaryUploadService::Result& result,
const DeepScanningClientResponse& response) {
// Don't record UMA metrics for this result.
if (result == BinaryUploadService::Result::UNAUTHORIZED)
return;
bool dlp_verdict_success = response.has_dlp_scan_verdict()
? response.dlp_scan_verdict().status() ==
DlpDeepScanningVerdict::SUCCESS
: true;
bool malware_verdict_success =
response.has_malware_scan_verdict()
? response.malware_scan_verdict().verdict() !=
MalwareDeepScanningVerdict::VERDICT_UNSPECIFIED
: true;
bool success = dlp_verdict_success && malware_verdict_success;
std::string result_value = BinaryUploadServiceResultToString(result, success);
// Update |success| so non-SUCCESS results don't log the bytes/sec metric.
success &= (result == BinaryUploadService::Result::SUCCESS);
RecordDeepScanMetrics(access_point, duration, total_bytes, result_value,
success);
}
void RecordDeepScanMetrics(DeepScanAccessPoint access_point,
base::TimeDelta duration,
int64_t total_bytes,
const std::string& result,
bool success) {
// Don't record metrics if the duration is unusable.
if (duration.InMilliseconds() == 0)
return;
std::string access_point_string = DeepScanAccessPointToString(access_point);
if (success) {
base::UmaHistogramCustomCounts(
"SafeBrowsing.DeepScan." + access_point_string + ".BytesPerSeconds",
(1000 * total_bytes) / duration.InMilliseconds(),
/*min=*/kMinBytesPerSecond,
/*max=*/kMaxBytesPerSecond,
/*buckets=*/50);
}
// The scanning timeout is 5 minutes, so the bucket maximum time is 30 minutes
// in order to be lenient and avoid having lots of data in the overlow bucket.
base::UmaHistogramCustomTimes("SafeBrowsing.DeepScan." + access_point_string +
"." + result + ".Duration",
duration, base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(30), 50);
base::UmaHistogramCustomTimes(
"SafeBrowsing.DeepScan." + access_point_string + ".Duration", duration,
base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(30),
50);
}
std::array<const base::FilePath::CharType*, 21> SupportedDlpFileTypes() {
// Keep sorted for efficient access.
static constexpr const std::array<const base::FilePath::CharType*, 21>
kSupportedDLPFileTypes = {
FILE_PATH_LITERAL(".7z"), FILE_PATH_LITERAL(".bzip"),
FILE_PATH_LITERAL(".cab"), FILE_PATH_LITERAL(".doc"),
FILE_PATH_LITERAL(".docx"), FILE_PATH_LITERAL(".eps"),
FILE_PATH_LITERAL(".gzip"), FILE_PATH_LITERAL(".odt"),
FILE_PATH_LITERAL(".pdf"), FILE_PATH_LITERAL(".ppt"),
FILE_PATH_LITERAL(".pptx"), FILE_PATH_LITERAL(".ps"),
FILE_PATH_LITERAL(".rar"), FILE_PATH_LITERAL(".rtf"),
FILE_PATH_LITERAL(".tar"), FILE_PATH_LITERAL(".txt"),
FILE_PATH_LITERAL(".wpd"), FILE_PATH_LITERAL(".xls"),
FILE_PATH_LITERAL(".xlsx"), FILE_PATH_LITERAL(".xps"),
FILE_PATH_LITERAL(".zip")};
// TODO: Replace this DCHECK with a static assert once std::is_sorted is
// constexpr in C++20.
DCHECK(std::is_sorted(
kSupportedDLPFileTypes.begin(), kSupportedDLPFileTypes.end(),
[](const base::FilePath::StringType& a,
const base::FilePath::StringType& b) { return a.compare(b) < 0; }));
return kSupportedDLPFileTypes;
}
bool FileTypeSupported(bool for_malware_scan,
bool for_dlp_scan,
const base::FilePath& path) {
// At least one of the booleans needs to be true.
DCHECK(for_malware_scan || for_dlp_scan);
// Accept any file type for malware scans.
if (for_malware_scan)
return true;
// Accept any file type in the supported list for DLP scans.
if (for_dlp_scan) {
base::FilePath::StringType extension(path.FinalExtension());
std::transform(extension.begin(), extension.end(), extension.begin(),
tolower);
auto dlp_types = SupportedDlpFileTypes();
return std::binary_search(dlp_types.begin(), dlp_types.end(), extension);
}
return false;
}
DeepScanningClientResponse SimpleDeepScanningClientResponseForTesting(
base::Optional<bool> dlp_success,
base::Optional<bool> malware_success) {
DeepScanningClientResponse response;
if (dlp_success.has_value()) {
response.mutable_dlp_scan_verdict()->set_status(
DlpDeepScanningVerdict::SUCCESS);
if (!dlp_success.value()) {
DlpDeepScanningVerdict::TriggeredRule* rule =
response.mutable_dlp_scan_verdict()->add_triggered_rules();
rule->set_rule_name("rule");
rule->set_action(DlpDeepScanningVerdict::TriggeredRule::BLOCK);
}
}
if (malware_success.has_value()) {
if (malware_success.value()) {
response.mutable_malware_scan_verdict()->set_verdict(
MalwareDeepScanningVerdict::CLEAN);
} else {
response.mutable_malware_scan_verdict()->set_verdict(
MalwareDeepScanningVerdict::MALWARE);
}
}
return response;
}
std::string BinaryUploadServiceResultToString(
const BinaryUploadService::Result& result,
bool success) {
switch (result) {
case BinaryUploadService::Result::SUCCESS:
if (success)
return "Success";
else
return "FailedToGetVerdict";
case BinaryUploadService::Result::UPLOAD_FAILURE:
return "UploadFailure";
case BinaryUploadService::Result::TIMEOUT:
return "Timeout";
case BinaryUploadService::Result::FILE_TOO_LARGE:
return "FileTooLarge";
case BinaryUploadService::Result::FAILED_TO_GET_TOKEN:
return "FailedToGetToken";
case BinaryUploadService::Result::UNKNOWN:
return "Unknown";
case BinaryUploadService::Result::UNAUTHORIZED:
return "";
case BinaryUploadService::Result::FILE_ENCRYPTED:
return "FileEncrypted";
case BinaryUploadService::Result::UNSUPPORTED_FILE_TYPE:
return "UnsupportedFileType";
}
}
} // namespace safe_browsing