blob: 53ec6070743e9211ecad104a734a52b223112221 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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/extension_telemetry/extension_telemetry_uploader.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
#include "components/safe_browsing/core/common/utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace safe_browsing {
namespace {
constexpr const char kUploadUrl[] =
"https://safebrowsing.google.com/safebrowsing/clientreport/crx-telemetry";
constexpr net::NetworkTrafficAnnotationTag
kSafeBrowsingExtensionTelemetryTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("safe_browsing_extension_telemetry",
R"(
semantics {
sender: "Safe Browsing Extension Telemetry"
description:
"Collects information about installed browser extensions and extension "
"related events (e.g., API invocations). Sends this information to "
"Google to help detect malware extensions."
trigger:
"Periodic upload of telemetry data once every few hours."
data:
"A list of currently installed extensions (id, name, version) along "
"with a list of extension event data associated with each extension."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "Safe Browsing cookie store"
setting:
"Users can enable this feature by selecting 'Enhanced protection' "
"under the Security->Safe Browsing setting. The feature is disabled by "
"default."
chrome_policy {
SafeBrowsingProtectionLevel {
SafeBrowsingProtectionLevel: 0
}
}
}
comments:
"SafeBrowsingProtectionLevel value of 0 or 1 disables the extension "
"telemetry feature. A value of 2 enables the feature. The feature is "
"disabled by default."
)");
// Constants associated with exponential backoff. On each failure, we will
// increase the backoff by |kBackoffFactor|, starting from
// |kInitialBackoffSeconds|. If we fail after |kMaxRetryAttempts| retries, the
// upload fails.
const int kInitialBackoffSeconds = 1;
const int kBackoffFactor = 2;
const int kMaxRetryAttempts = 2;
void RecordUploadSize(size_t size) {
base::UmaHistogramCounts1M("SafeBrowsing.ExtensionTelemetry.UploadSize",
size);
}
void RecordUploadSuccess(bool success) {
base::UmaHistogramBoolean("SafeBrowsing.ExtensionTelemetry.UploadSuccess",
success);
}
void RecordUploadRetries(int num_retries) {
base::UmaHistogramExactLinear(
"SafeBrowsing.ExtensionTelemetry.RetriesTillUploadSuccess", num_retries,
kMaxRetryAttempts);
}
void RecordNetworkResponseCodeOrError(int code_or_error) {
base::UmaHistogramSparse(
"SafeBrowsing.ExtensionTelemetry.NetworkRequestResponseCodeOrError",
code_or_error);
}
void RecordUploadDuration(bool success, base::TimeDelta delta) {
if (success) {
base::UmaHistogramMediumTimes(
"SafeBrowsing.ExtensionTelemetry.SuccessfulUploadDuration", delta);
} else {
base::UmaHistogramMediumTimes(
"SafeBrowsing.ExtensionTelemetry.FailedUploadDuration", delta);
}
}
} // namespace
ExtensionTelemetryUploader::~ExtensionTelemetryUploader() = default;
ExtensionTelemetryUploader::ExtensionTelemetryUploader(
OnUploadCallback callback,
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
std::unique_ptr<std::string> upload_data,
std::unique_ptr<SafeBrowsingTokenFetcher> token_fetcher)
: callback_(std::move(callback)),
url_loader_factory_(url_loader_factory),
upload_data_(std::move(upload_data)),
current_backoff_(base::Seconds(kInitialBackoffSeconds)),
num_upload_retries_(0),
token_fetcher_(std::move(token_fetcher)) {}
void ExtensionTelemetryUploader::Start() {
upload_start_time_ = base::TimeTicks::Now();
RecordUploadSize(upload_data_->size());
MaybeSendRequestWithAccessToken();
}
// static
std::string ExtensionTelemetryUploader::GetUploadURLForTest() {
return kUploadUrl;
}
void ExtensionTelemetryUploader::MaybeSendRequestWithAccessToken() {
if (token_fetcher_) {
token_fetcher_->Start(base::BindOnce(
&ExtensionTelemetryUploader::SendRequest, weak_factory_.GetWeakPtr()));
} else {
SendRequest(std::string());
}
}
void ExtensionTelemetryUploader::SendRequest(const std::string& access_token) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GURL(kUploadUrl);
resource_request->method = "POST";
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
if (!access_token.empty()) {
SetAccessTokenAndClearCookieInResourceRequest(resource_request.get(),
access_token);
} else {
resource_request->credentials_mode =
network::mojom::CredentialsMode::kInclude;
}
url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request),
kSafeBrowsingExtensionTelemetryTrafficAnnotation);
url_loader_->SetAllowHttpErrorResults(true);
url_loader_->AttachStringForUpload(*upload_data_, "application/octet-stream");
url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&ExtensionTelemetryUploader::OnURLLoaderComplete,
weak_factory_.GetWeakPtr()));
}
void ExtensionTelemetryUploader::OnURLLoaderComplete(
std::unique_ptr<std::string> /* response_body */) {
int response_code = 0;
if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
response_code = url_loader_->ResponseInfo()->headers->response_code();
RetryOrFinish(url_loader_->NetError(), response_code);
}
void ExtensionTelemetryUploader::RetryOrFinish(int net_error,
int response_code) {
RecordNetworkResponseCodeOrError(net_error == net::OK ? response_code
: net_error);
if (net_error == net::OK && response_code == net::HTTP_OK) {
RecordUploadSuccess(/*success*/ true);
RecordUploadRetries(num_upload_retries_);
RecordUploadDuration(/*success*/ true,
base::TimeTicks::Now() - upload_start_time_);
// Callback may delete the uploader, so no touching anything after this.
std::move(callback_).Run(/*success=*/true);
} else {
if (response_code < 500 || num_upload_retries_ >= kMaxRetryAttempts) {
RecordUploadSuccess(/*success*/ false);
RecordUploadDuration(/*success*/ false,
base::TimeTicks::Now() - upload_start_time_);
// Callback may delete the uploader, so no touching anything after this.
std::move(callback_).Run(/*success=*/false);
} else {
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&ExtensionTelemetryUploader::MaybeSendRequestWithAccessToken,
weak_factory_.GetWeakPtr()),
current_backoff_);
current_backoff_ *= kBackoffFactor;
num_upload_retries_++;
}
}
}
} // namespace safe_browsing