summaryrefslogtreecommitdiffstats
path: root/chrome/browser/safe_browsing/download_protection_service.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/safe_browsing/download_protection_service.cc')
-rw-r--r--chrome/browser/safe_browsing/download_protection_service.cc412
1 files changed, 276 insertions, 136 deletions
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index c1ce0f3..668290b 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -8,11 +8,15 @@
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
+#include "base/string_util.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
+#include "chrome/browser/safe_browsing/signature_util.h"
#include "chrome/common/net/http_return.h"
#include "chrome/common/safe_browsing/csd.pb.h"
#include "content/browser/browser_thread.h"
+#include "content/browser/download/download_item.h"
#include "content/public/common/url_fetcher.h"
+#include "content/public/common/url_fetcher_delegate.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
@@ -22,63 +26,110 @@ namespace safe_browsing {
const char DownloadProtectionService::kDownloadRequestUrl[] =
"https://sb-ssl.google.com/safebrowsing/clientreport/download";
+namespace {
+bool IsBinaryFile(const FilePath& file) {
+ return (file.MatchesExtension(FILE_PATH_LITERAL(".exe")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".cab")) ||
+ file.MatchesExtension(FILE_PATH_LITERAL(".msi")));
+}
+} // namespace
+
DownloadProtectionService::DownloadInfo::DownloadInfo()
: total_bytes(0), user_initiated(false) {}
DownloadProtectionService::DownloadInfo::~DownloadInfo() {}
-DownloadProtectionService::DownloadProtectionService(
- SafeBrowsingService* sb_service,
- net::URLRequestContextGetter* request_context_getter)
- : sb_service_(sb_service),
- request_context_getter_(request_context_getter),
- enabled_(false) {}
-
-DownloadProtectionService::~DownloadProtectionService() {
- STLDeleteContainerPairFirstPointers(download_requests_.begin(),
- download_requests_.end());
- download_requests_.clear();
+// static
+DownloadProtectionService::DownloadInfo
+DownloadProtectionService::DownloadInfo::FromDownloadItem(
+ const DownloadItem& item) {
+ DownloadInfo download_info;
+ download_info.local_file = item.full_path();
+ download_info.download_url_chain = item.url_chain();
+ download_info.referrer_url = item.referrer_url();
+ // TODO(bryner): Fill in the hash (we shouldn't compute it again)
+ download_info.total_bytes = item.total_bytes();
+ // TODO(bryner): Populate user_initiated
+ return download_info;
}
-void DownloadProtectionService::SetEnabled(bool enabled) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- BrowserThread::PostTask(
- BrowserThread::IO,
- FROM_HERE,
- base::Bind(&DownloadProtectionService::SetEnabledOnIOThread,
- this, enabled));
-}
-
-void DownloadProtectionService::SetEnabledOnIOThread(bool enabled) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- if (enabled == enabled_) {
- return;
+class DownloadProtectionService::CheckClientDownloadRequest
+ : public base::RefCountedThreadSafe<
+ DownloadProtectionService::CheckClientDownloadRequest,
+ BrowserThread::DeleteOnUIThread>,
+ public content::URLFetcherDelegate {
+ public:
+ CheckClientDownloadRequest(const DownloadInfo& info,
+ const CheckDownloadCallback& callback,
+ DownloadProtectionService* service,
+ SafeBrowsingService* sb_service)
+ : info_(info),
+ callback_(callback),
+ service_(service),
+ sb_service_(sb_service),
+ pingback_enabled_(service_->enabled()) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
- enabled_ = enabled;
- if (!enabled_) {
- for (DownloadRequests::iterator it = download_requests_.begin();
- it != download_requests_.end(); ++it) {
- it->second.Run(SAFE);
+
+ void Start() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // TODO(noelutz): implement some cache to make sure we don't issue the same
+ // request over and over again if a user downloads the same binary multiple
+ // times.
+ if (info_.download_url_chain.empty()) {
+ RecordStats(REASON_INVALID_URL);
+ PostFinishTask(SAFE);
+ return;
+ }
+ const GURL& final_url = info_.download_url_chain.back();
+ if (!final_url.is_valid() || final_url.is_empty() ||
+ !final_url.SchemeIs("http")) {
+ RecordStats(REASON_INVALID_URL);
+ PostFinishTask(SAFE);
+ return; // For now we only support HTTP download URLs.
}
- STLDeleteContainerPairFirstPointers(download_requests_.begin(),
- download_requests_.end());
- download_requests_.clear();
- }
-}
-void DownloadProtectionService::OnURLFetchComplete(
- const content::URLFetcher* source) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- scoped_ptr<const content::URLFetcher> s(source);
- if (download_requests_.find(source) != download_requests_.end()) {
- CheckDownloadCallback callback = download_requests_[source];
- download_requests_.erase(source);
- if (!enabled_) {
- // SafeBrowsing got disabled. We can't do anything. Note: the request
- // object will be deleted.
- RecordStats(REASON_SB_DISABLED);
+ if (!IsBinaryFile(info_.local_file)) {
+ RecordStats(REASON_NOT_BINARY_FILE);
+ PostFinishTask(SAFE);
return;
}
+
+ // Compute features from the file contents. Note that we record histograms
+ // based on the result, so this runs regardless of whether the pingbacks
+ // are enabled. Since we do blocking I/O, this happens on the file thread.
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&CheckClientDownloadRequest::ExtractFileFeatures, this));
+ }
+
+ // Canceling a request will cause us to always report the result as SAFE.
+ // In addition, the DownloadProtectionService will not be notified when the
+ // request finishes, so it must drop its reference after calling Cancel.
+ void Cancel() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ service_ = NULL;
+ if (fetcher_.get()) {
+ // The DownloadProtectionService is going to release its reference, so we
+ // might be destroyed before the URLFetcher completes. Cancel the
+ // fetcher so it does not try to invoke OnURLFetchComplete.
+ FinishRequest(SAFE);
+ fetcher_.reset();
+ }
+ // Note: If there is no fetcher, then some callback is still holding a
+ // reference to this object. We'll eventually wind up in some method on
+ // the UI thread that will call FinishRequest() and run the callback.
+ }
+
+ // From the content::URLFetcherDelegate interface.
+ virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(source, fetcher_.get());
+ VLOG(2) << "Received a response for URL: "
+ << info_.download_url_chain.back() << ": success="
+ << source->GetStatus().is_success() << " response_code="
+ << source->GetResponseCode();
DownloadCheckResultReason reason = REASON_MAX;
reason = REASON_SERVER_PING_FAILED;
if (source->GetStatus().is_success() &&
@@ -92,118 +143,207 @@ void DownloadProtectionService::OnURLFetchComplete(
reason = REASON_INVALID_RESPONSE_PROTO;
}
}
- BrowserThread::PostTask(
- BrowserThread::UI,
- FROM_HERE,
- base::Bind(&DownloadProtectionService::EndCheckClientDownload,
- this, SAFE, reason, callback));
- } else {
- NOTREACHED();
- }
-}
-bool DownloadProtectionService::CheckClientDownload(
- const DownloadInfo& info,
- const CheckDownloadCallback& callback) {
- // TODO(noelutz): implement some cache to make sure we don't issue the same
- // request over and over again if a user downloads the same binary multiple
- // times.
- if (info.download_url_chain.empty()) {
- RecordStats(REASON_INVALID_URL);
- return true;
- }
- const GURL& final_url = info.download_url_chain.back();
- if (!final_url.is_valid() || final_url.is_empty() ||
- !final_url.SchemeIs("http")) {
- RecordStats(REASON_INVALID_URL);
- return true; // For now we only support HTTP download URLs.
+ if (reason != REASON_MAX) {
+ RecordStats(reason);
+ }
+ // We don't need the fetcher anymore.
+ fetcher_.reset();
+ FinishRequest(SAFE);
}
- // TODO(noelutz): DownloadInfo should also contain the IP address of every
- // URL in the redirect chain. We also should check whether the download URL
- // is hosted on the internal network.
- BrowserThread::PostTask(
- BrowserThread::IO,
- FROM_HERE,
- base::Bind(&DownloadProtectionService::StartCheckClientDownload,
- this, info, callback));
- return false;
-}
-void DownloadProtectionService::StartCheckClientDownload(
- const DownloadInfo& info,
- const CheckDownloadCallback& callback) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- if (!enabled_ || !sb_service_.get()) {
- // This is a hard fail. We won't even call the callback in this case.
- RecordStats(REASON_SB_DISABLED);
- return;
+ private:
+ friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
+ friend class DeleteTask<CheckClientDownloadRequest>;
+
+ virtual ~CheckClientDownloadRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
- DownloadCheckResultReason reason = REASON_MAX;
- for (size_t i = 0; i < info.download_url_chain.size(); ++i) {
- if (sb_service_->MatchDownloadWhitelistUrl(info.download_url_chain[i])) {
- reason = REASON_WHITELISTED_URL;
- break;
+
+ void ExtractFileFeatures() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ bool is_signed;
+ if (safe_browsing::signature_util::IsSigned(info_.local_file)) {
+ VLOG(2) << "Downloaded a signed binary: " << info_.local_file.value();
+ is_signed = true;
+ } else {
+ VLOG(2) << "Downloaded an unsigned binary: " << info_.local_file.value();
+ is_signed = false;
}
+ UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed);
+
+ // TODO(noelutz): DownloadInfo should also contain the IP address of every
+ // URL in the redirect chain. We also should check whether the download
+ // URL is hosted on the internal network.
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&CheckClientDownloadRequest::CheckWhitelists, this));
}
- if (sb_service_->MatchDownloadWhitelistUrl(info.referrer_url)) {
- reason = REASON_WHITELISTED_REFERRER;
- }
- // TODO(noelutz): check signature and CA against whitelist.
-
- ClientDownloadRequest request;
- request.set_url(info.download_url_chain.back().spec());
- request.mutable_digests()->set_sha256(info.sha256_hash);
- request.set_length(info.total_bytes);
- for (size_t i = 0; i < info.download_url_chain.size(); ++i) {
- ClientDownloadRequest::Resource* resource = request.add_resources();
- resource->set_url(info.download_url_chain[i].spec());
- if (i == info.download_url_chain.size() - 1) {
- // The last URL in the chain is the download URL.
- resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
- resource->set_referrer(info.referrer_url.spec());
+
+ void CheckWhitelists() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DownloadCheckResultReason reason = REASON_MAX;
+ if (!pingback_enabled_ || !sb_service_.get()) {
+ reason = REASON_SB_DISABLED;
} else {
- resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
+ for (size_t i = 0; i < info_.download_url_chain.size(); ++i) {
+ const GURL& url = info_.download_url_chain[i];
+ if (url.is_valid() && sb_service_->MatchDownloadWhitelistUrl(url)) {
+ reason = REASON_WHITELISTED_URL;
+ break;
+ }
+ }
+ if (info_.referrer_url.is_valid() &&
+ sb_service_->MatchDownloadWhitelistUrl(info_.referrer_url)) {
+ reason = REASON_WHITELISTED_REFERRER;
+ }
+ }
+ if (reason != REASON_MAX) {
+ RecordStats(reason);
+ PostFinishTask(SAFE);
+ return;
}
- // TODO(noelutz): fill out the remote IP addresses.
+
+ // TODO(noelutz): check signature and CA against whitelist.
+
+ // The URLFetcher is owned by the UI thread, so post a message to
+ // start the pingback.
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&CheckClientDownloadRequest::SendRequest, this));
}
- request.set_user_initiated(info.user_initiated);
- std::string request_data;
- if (!request.SerializeToString(&request_data)) {
- reason = REASON_INVALID_REQUEST_PROTO;
+
+ void SendRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // This is our last chance to check whether the request has been canceled
+ // before sending it.
+ if (!service_) {
+ FinishRequest(SAFE);
+ return;
+ }
+
+ ClientDownloadRequest request;
+ request.set_url(info_.download_url_chain.back().spec());
+ request.mutable_digests()->set_sha256(info_.sha256_hash);
+ request.set_length(info_.total_bytes);
+ for (size_t i = 0; i < info_.download_url_chain.size(); ++i) {
+ ClientDownloadRequest::Resource* resource = request.add_resources();
+ resource->set_url(info_.download_url_chain[i].spec());
+ if (i == info_.download_url_chain.size() - 1) {
+ // The last URL in the chain is the download URL.
+ resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
+ resource->set_referrer(info_.referrer_url.spec());
+ } else {
+ resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
+ }
+ // TODO(noelutz): fill out the remote IP addresses.
+ }
+ request.set_user_initiated(info_.user_initiated);
+ std::string request_data;
+ if (!request.SerializeToString(&request_data)) {
+ RecordStats(REASON_INVALID_REQUEST_PROTO);
+ FinishRequest(SAFE);
+ return;
+ }
+
+ VLOG(2) << "Sending a request for URL: "
+ << info_.download_url_chain.back();
+ fetcher_.reset(content::URLFetcher::Create(0 /* ID used for testing */,
+ GURL(kDownloadRequestUrl),
+ content::URLFetcher::POST,
+ this));
+ fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
+ fetcher_->SetRequestContext(service_->request_context_getter_.get());
+ fetcher_->SetUploadData("application/octet-stream", request_data);
+ fetcher_->Start();
}
- if (reason != REASON_MAX) {
- // We stop here because the download is considered safe.
+ void PostFinishTask(DownloadCheckResult result) {
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
- base::Bind(&DownloadProtectionService::EndCheckClientDownload,
- this, SAFE, reason, callback));
- return;
+ base::Bind(&CheckClientDownloadRequest::FinishRequest, this, result));
}
- content::URLFetcher* fetcher = content::URLFetcher::Create(
- 0 /* ID used for testing */, GURL(kDownloadRequestUrl),
- content::URLFetcher::POST, this);
- download_requests_[fetcher] = callback;
- fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
- fetcher->SetRequestContext(request_context_getter_.get());
- fetcher->SetUploadData("application/octet-stream", request_data);
- fetcher->Start();
+ void FinishRequest(DownloadCheckResult result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (service_) {
+ callback_.Run(result);
+ service_->RequestFinished(this);
+ } else {
+ callback_.Run(SAFE);
+ }
+ }
+
+ void RecordStats(DownloadCheckResultReason reason) {
+ UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats",
+ reason,
+ REASON_MAX);
+ }
+
+ DownloadInfo info_;
+ CheckDownloadCallback callback_;
+ // Will be NULL if the request has been canceled.
+ DownloadProtectionService* service_;
+ scoped_refptr<SafeBrowsingService> sb_service_;
+ bool pingback_enabled_;
+ scoped_ptr<content::URLFetcher> fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(CheckClientDownloadRequest);
+};
+
+DownloadProtectionService::DownloadProtectionService(
+ SafeBrowsingService* sb_service,
+ net::URLRequestContextGetter* request_context_getter)
+ : sb_service_(sb_service),
+ request_context_getter_(request_context_getter),
+ enabled_(false) {}
+
+DownloadProtectionService::~DownloadProtectionService() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ CancelPendingRequests();
+}
+
+void DownloadProtectionService::SetEnabled(bool enabled) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (enabled == enabled_) {
+ return;
+ }
+ enabled_ = enabled;
+ if (!enabled_) {
+ CancelPendingRequests();
+ }
}
-void DownloadProtectionService::EndCheckClientDownload(
- DownloadCheckResult result,
- DownloadCheckResultReason reason,
+void DownloadProtectionService::CheckClientDownload(
+ const DownloadProtectionService::DownloadInfo& info,
const CheckDownloadCallback& callback) {
+ scoped_refptr<CheckClientDownloadRequest> request(
+ new CheckClientDownloadRequest(info, callback, this, sb_service_));
+ download_requests_.insert(request);
+ request->Start();
+}
+
+void DownloadProtectionService::CancelPendingRequests() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- RecordStats(reason);
- callback.Run(result);
+ for (std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
+ download_requests_.begin();
+ it != download_requests_.end(); ++it) {
+ (*it)->Cancel();
+ }
+ download_requests_.clear();
}
-void DownloadProtectionService::RecordStats(DownloadCheckResultReason reason) {
- UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats",
- reason,
- REASON_MAX);
+void DownloadProtectionService::RequestFinished(
+ CheckClientDownloadRequest* request) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
+ download_requests_.find(request);
+ DCHECK(it != download_requests_.end());
+ download_requests_.erase(*it);
}
+
} // namespace safe_browsing