blob: bc86e964b18b8b4279b557d0e91bdafcc63f432b [file] [log] [blame]
// Copyright 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/binary_upload_service.h"
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_fcm_service.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h"
#include "chrome/browser/safe_browsing/dm_token_utils.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/proto/webprotect.pb.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_status_code.h"
namespace safe_browsing {
namespace {
const int kScanningTimeoutSeconds = 5 * 60; // 5 minutes
const char kSbBinaryUploadUrl[] =
"https://safebrowsing.google.com/safebrowsing/uploads/scan";
} // namespace
BinaryUploadService::BinaryUploadService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
Profile* profile)
: url_loader_factory_(url_loader_factory),
binary_fcm_service_(BinaryFCMService::Create(profile)),
profile_(profile),
weakptr_factory_(this) {}
BinaryUploadService::BinaryUploadService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::unique_ptr<BinaryFCMService> binary_fcm_service)
: url_loader_factory_(url_loader_factory),
binary_fcm_service_(std::move(binary_fcm_service)),
profile_(nullptr),
weakptr_factory_(this) {}
BinaryUploadService::~BinaryUploadService() {}
void BinaryUploadService::MaybeUploadForDeepScanning(
std::unique_ptr<BinaryUploadService::Request> request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!can_upload_data_.has_value()) {
IsAuthorized(
base::BindOnce(&BinaryUploadService::MaybeUploadForDeepScanningCallback,
weakptr_factory_.GetWeakPtr(), std::move(request)));
return;
}
MaybeUploadForDeepScanningCallback(std::move(request),
can_upload_data_.value());
}
void BinaryUploadService::MaybeUploadForDeepScanningCallback(
std::unique_ptr<BinaryUploadService::Request> request,
bool authorized) {
// Ignore the request if the browser cannot upload data.
if (!authorized) {
// TODO(crbug/1028133): Add extra logic to handle UX for non-authorized
// users.
request->FinishRequest(Result::UNAUTHORIZED, DeepScanningClientResponse());
return;
}
UploadForDeepScanning(std::move(request));
}
void BinaryUploadService::UploadForDeepScanning(
std::unique_ptr<BinaryUploadService::Request> request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Request* raw_request = request.get();
active_requests_[raw_request] = std::move(request);
start_times_[raw_request] = base::TimeTicks::Now();
if (!binary_fcm_service_) {
base::PostTask(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&BinaryUploadService::FinishRequest,
weakptr_factory_.GetWeakPtr(), raw_request,
Result::FAILED_TO_GET_TOKEN,
DeepScanningClientResponse()));
return;
}
std::string token = base::RandBytesAsString(128);
token = base::HexEncode(token.data(), token.size());
active_tokens_[raw_request] = token;
binary_fcm_service_->SetCallbackForToken(
token, base::BindRepeating(&BinaryUploadService::OnGetResponse,
weakptr_factory_.GetWeakPtr(), raw_request));
raw_request->set_request_token(std::move(token));
binary_fcm_service_->GetInstanceID(
base::BindOnce(&BinaryUploadService::OnGetInstanceID,
weakptr_factory_.GetWeakPtr(), raw_request));
active_timers_[raw_request] = std::make_unique<base::OneShotTimer>();
active_timers_[raw_request]->Start(
FROM_HERE, base::TimeDelta::FromSeconds(kScanningTimeoutSeconds),
base::BindOnce(&BinaryUploadService::OnTimeout,
weakptr_factory_.GetWeakPtr(), raw_request));
}
void BinaryUploadService::OnGetInstanceID(Request* request,
const std::string& instance_id) {
if (!IsActive(request))
return;
if (instance_id == BinaryFCMService::kInvalidId) {
FinishRequest(request, Result::FAILED_TO_GET_TOKEN,
DeepScanningClientResponse());
return;
}
request->set_fcm_token(instance_id);
request->GetRequestData(base::BindOnce(&BinaryUploadService::OnGetRequestData,
weakptr_factory_.GetWeakPtr(),
request));
}
void BinaryUploadService::OnGetRequestData(Request* request,
Result result,
const Request::Data& data) {
if (!IsActive(request))
return;
if (result != Result::SUCCESS) {
FinishRequest(request, result, DeepScanningClientResponse());
return;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("safe_browsing_binary_upload", R"(
semantics {
sender: "Safe Browsing Download Protection"
description:
"For users with the enterprise policy "
"SendFilesForMalwareCheck set, when a file is "
"downloaded, Chrome will upload that file to Safe Browsing for "
"detailed scanning."
trigger:
"The browser will upload the file to Google when "
"the user downloads a file, and the enterprise policy "
"SendFilesForMalwareCheck is set."
data:
"The downloaded file."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "Safe Browsing Cookie Store"
setting: "This is disabled by default an can only be enabled by "
"policy."
chrome_policy {
SendFilesForMalwareCheck {
SendFilesForMalwareCheck: 0
}
}
chrome_policy {
SendFilesForMalwareCheck {
SendFilesForMalwareCheck: 1
}
}
}
comments: "Setting SendFilesForMalwareCheck to 0 (Do not scan "
"downloads) or 1 (Forbid the scanning of downloads) will disable "
"this feature"
)");
std::string metadata;
request->deep_scanning_request().SerializeToString(&metadata);
base::Base64Encode(metadata, &metadata);
auto upload_request = MultipartUploadRequest::Create(
url_loader_factory_, GURL(kSbBinaryUploadUrl), metadata, data.contents,
traffic_annotation,
base::BindOnce(&BinaryUploadService::OnUploadComplete,
weakptr_factory_.GetWeakPtr(), request));
upload_request->Start();
active_uploads_[request] = std::move(upload_request);
}
void BinaryUploadService::OnUploadComplete(Request* request,
bool success,
const std::string& response_data) {
if (!IsActive(request))
return;
if (!success) {
FinishRequest(request, Result::UPLOAD_FAILURE,
DeepScanningClientResponse());
return;
}
DeepScanningClientResponse response;
if (!response.ParseFromString(response_data)) {
FinishRequest(request, Result::UPLOAD_FAILURE,
DeepScanningClientResponse());
return;
}
active_uploads_.erase(request);
// Synchronous scans can return results in the initial response proto, so
// check for those.
OnGetResponse(request, response);
}
void BinaryUploadService::OnGetResponse(Request* request,
DeepScanningClientResponse response) {
if (!IsActive(request))
return;
if (response.has_dlp_scan_verdict()) {
received_dlp_verdicts_[request].reset(response.release_dlp_scan_verdict());
}
if (response.has_malware_scan_verdict()) {
received_malware_verdicts_[request].reset(
response.release_malware_scan_verdict());
}
MaybeFinishRequest(request);
}
void BinaryUploadService::MaybeFinishRequest(Request* request) {
bool requested_dlp_scan_response =
request->deep_scanning_request().has_dlp_scan_request();
auto received_dlp_response = received_dlp_verdicts_.find(request);
if (requested_dlp_scan_response &&
received_dlp_response == received_dlp_verdicts_.end()) {
return;
}
bool requested_malware_scan_response =
request->deep_scanning_request().has_malware_scan_request();
auto received_malware_response = received_malware_verdicts_.find(request);
if (requested_malware_scan_response &&
received_malware_response == received_malware_verdicts_.end()) {
return;
}
DeepScanningClientResponse response;
if (requested_dlp_scan_response) {
// Transfers ownership of the DLP response to |response|.
response.set_allocated_dlp_scan_verdict(
received_dlp_response->second.release());
}
if (requested_malware_scan_response) {
// Transfers ownership of the malware response to |response|.
response.set_allocated_malware_scan_verdict(
received_malware_response->second.release());
}
FinishRequest(request, Result::SUCCESS, std::move(response));
}
void BinaryUploadService::OnTimeout(Request* request) {
if (IsActive(request))
FinishRequest(request, Result::TIMEOUT, DeepScanningClientResponse());
}
void BinaryUploadService::FinishRequest(Request* request,
Result result,
DeepScanningClientResponse response) {
RecordRequestMetrics(request, result, response);
request->FinishRequest(result, response);
active_requests_.erase(request);
active_timers_.erase(request);
active_uploads_.erase(request);
received_malware_verdicts_.erase(request);
received_dlp_verdicts_.erase(request);
auto token_it = active_tokens_.find(request);
if (token_it != active_tokens_.end()) {
binary_fcm_service_->ClearCallbackForToken(token_it->second);
active_tokens_.erase(token_it);
}
}
void BinaryUploadService::RecordRequestMetrics(
Request* request,
Result result,
const DeepScanningClientResponse& response) {
base::UmaHistogramEnumeration("SafeBrowsingBinaryUploadRequest.Result",
result);
base::UmaHistogramCustomTimes("SafeBrowsingBinaryUploadRequest.Duration",
base::TimeTicks::Now() - start_times_[request],
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromMinutes(6), 50);
if (response.has_malware_scan_verdict()) {
base::UmaHistogramBoolean("SafeBrowsingBinaryUploadRequest.MalwareResult",
response.malware_scan_verdict().status() ==
MalwareDeepScanningVerdict::SUCCESS);
}
if (response.has_dlp_scan_verdict()) {
base::UmaHistogramBoolean("SafeBrowsingBinaryUploadRequest.DlpResult",
response.dlp_scan_verdict().status() ==
DlpDeepScanningVerdict::SUCCESS);
}
}
BinaryUploadService::Request::Data::Data() = default;
BinaryUploadService::Request::Request(Callback callback)
: callback_(std::move(callback)) {}
BinaryUploadService::Request::~Request() {}
void BinaryUploadService::Request::set_request_dlp_scan(
DlpDeepScanningClientRequest dlp_request) {
*deep_scanning_request_.mutable_dlp_scan_request() = std::move(dlp_request);
}
void BinaryUploadService::Request::set_request_malware_scan(
MalwareDeepScanningClientRequest malware_request) {
*deep_scanning_request_.mutable_malware_scan_request() =
std::move(malware_request);
}
void BinaryUploadService::Request::set_fcm_token(const std::string& token) {
deep_scanning_request_.set_fcm_notification_token(token);
}
void BinaryUploadService::Request::set_dm_token(const std::string& token) {
deep_scanning_request_.set_dm_token(token);
}
void BinaryUploadService::Request::set_request_token(const std::string& token) {
deep_scanning_request_.set_request_token(token);
}
void BinaryUploadService::Request::FinishRequest(
Result result,
DeepScanningClientResponse response) {
std::move(callback_).Run(result, response);
}
bool BinaryUploadService::IsActive(Request* request) {
return (active_requests_.find(request) != active_requests_.end());
}
class ValidateDataUploadRequest : public BinaryUploadService::Request {
public:
explicit ValidateDataUploadRequest(BinaryUploadService::Callback callback)
: BinaryUploadService::Request(std::move(callback)) {}
ValidateDataUploadRequest(const ValidateDataUploadRequest&) = delete;
ValidateDataUploadRequest& operator=(const ValidateDataUploadRequest&) =
delete;
~ValidateDataUploadRequest() override = default;
private:
// BinaryUploadService::Request implementation.
void GetRequestData(DataCallback callback) override;
};
inline void ValidateDataUploadRequest::GetRequestData(DataCallback callback) {
std::move(callback).Run(BinaryUploadService::Result::SUCCESS,
BinaryUploadService::Request::Data());
}
void BinaryUploadService::IsAuthorized(AuthorizationCallback callback) {
// Start |timer_| on the first call to IsAuthorized. This is necessary in
// order to invalidate the authorization every 24 hours.
if (!timer_.IsRunning()) {
timer_.Start(FROM_HERE, base::TimeDelta::FromHours(24), this,
&BinaryUploadService::ResetAuthorizationData);
}
if (!can_upload_data_.has_value()) {
// Send a request to check if the browser can upload data.
if (!pending_validate_data_upload_request_) {
auto dm_token = GetDMToken(profile_);
if (!dm_token.is_valid()) {
std::move(callback).Run(false);
return;
}
pending_validate_data_upload_request_ = true;
auto request = std::make_unique<ValidateDataUploadRequest>(base::BindOnce(
&BinaryUploadService::ValidateDataUploadRequestCallback,
weakptr_factory_.GetWeakPtr()));
request->set_dm_token(dm_token.value());
UploadForDeepScanning(std::move(request));
}
authorization_callbacks_.push_back(std::move(callback));
return;
}
std::move(callback).Run(can_upload_data_.value());
}
void BinaryUploadService::ValidateDataUploadRequestCallback(
BinaryUploadService::Result result,
DeepScanningClientResponse response) {
pending_validate_data_upload_request_ = false;
can_upload_data_ = result == BinaryUploadService::Result::SUCCESS;
RunAuthorizationCallbacks();
}
void BinaryUploadService::RunAuthorizationCallbacks() {
DCHECK(can_upload_data_.has_value());
for (auto& callback : authorization_callbacks_) {
std::move(callback).Run(can_upload_data_.value());
}
authorization_callbacks_.clear();
}
void BinaryUploadService::ResetAuthorizationData() {
// Setting |can_upload_data_| to base::nullopt will make the next call to
// IsAuthorized send out a request to validate data uploads.
can_upload_data_ = base::nullopt;
// Call IsAuthorized to update |can_upload_data_| right away.
IsAuthorized(base::DoNothing());
}
void BinaryUploadService::SetAuthForTesting(bool authorized) {
can_upload_data_ = authorized;
}
// static
bool BinaryUploadService::ShouldBlockFileSize(size_t file_size) {
int block_large_file_transfer = g_browser_process->local_state()->GetInteger(
prefs::kBlockLargeFileTransfer);
if (block_large_file_transfer !=
BlockLargeFileTransferValues::BLOCK_LARGE_DOWNLOADS &&
block_large_file_transfer !=
BlockLargeFileTransferValues::BLOCK_LARGE_UPLOADS_AND_DOWNLOADS)
return false;
return (file_size > kMaxUploadSizeBytes);
}
// static
GURL BinaryUploadService::GetUploadUrl() {
return GURL(kSbBinaryUploadUrl);
}
} // namespace safe_browsing