Commit bbf024ee authored by bryner@chromium.org's avatar bryner@chromium.org

Collect some histograms about signed binary downloads.

This hooks up the DownloadProtectionService to run after a download finishes.  For now, this does not send a download pingback since the flag defaults to off.


TEST=DownloadProtectionServiceTest
BUG=none

Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=107528

Review URL: http://codereview.chromium.org/8345033

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107674 0039d316-1c4b-4281-b951-d872f2087c98
parent 2fa31262
...@@ -156,8 +156,27 @@ bool ChromeDownloadManagerDelegate::ShouldOpenDownload(DownloadItem* item) { ...@@ -156,8 +156,27 @@ bool ChromeDownloadManagerDelegate::ShouldOpenDownload(DownloadItem* item) {
} }
bool ChromeDownloadManagerDelegate::ShouldCompleteDownload(DownloadItem* item) { bool ChromeDownloadManagerDelegate::ShouldCompleteDownload(DownloadItem* item) {
if (!IsExtensionDownload(item)) if (!IsExtensionDownload(item)) {
#if defined(ENABLE_SAFE_BROWSING)
// Begin the safe browsing download protection check.
SafeBrowsingService* sb_service =
g_browser_process->safe_browsing_service();
if (sb_service && sb_service->download_protection_service() &&
profile_->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
using safe_browsing::DownloadProtectionService;
sb_service->download_protection_service()->CheckClientDownload(
DownloadProtectionService::DownloadInfo::FromDownloadItem(*item),
base::Bind(
&ChromeDownloadManagerDelegate::CheckClientDownloadDone,
this, item->id()));
// For now, we won't delay the download for this.
}
#else
// Assume safe.
#endif
return true; return true;
}
scoped_refptr<CrxInstaller> crx_installer = scoped_refptr<CrxInstaller> crx_installer =
download_crx_util::OpenChromeExtension(profile_, *item); download_crx_util::OpenChromeExtension(profile_, *item);
...@@ -227,6 +246,12 @@ void ChromeDownloadManagerDelegate::UpdatePathForItemInPersistentStore( ...@@ -227,6 +246,12 @@ void ChromeDownloadManagerDelegate::UpdatePathForItemInPersistentStore(
download_history_->UpdateDownloadPath(item, new_path); download_history_->UpdateDownloadPath(item, new_path);
} }
void ChromeDownloadManagerDelegate::CheckClientDownloadDone(
int32 download_id,
safe_browsing::DownloadProtectionService::DownloadCheckResult result) {
// TODO(bryner): notify the user based on this result
}
void ChromeDownloadManagerDelegate::RemoveItemFromPersistentStore( void ChromeDownloadManagerDelegate::RemoveItemFromPersistentStore(
DownloadItem* item) { DownloadItem* item) {
download_history_->RemoveEntry(item); download_history_->RemoveEntry(item);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/task.h" #include "base/task.h"
#include "chrome/browser/safe_browsing/download_protection_service.h"
#include "content/public/browser/download_manager_delegate.h" #include "content/public/browser/download_manager_delegate.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
...@@ -134,6 +135,11 @@ class ChromeDownloadManagerDelegate ...@@ -134,6 +135,11 @@ class ChromeDownloadManagerDelegate
// service. // service.
void CheckDownloadHashDone(int32 download_id, bool is_dangerous_hash); void CheckDownloadHashDone(int32 download_id, bool is_dangerous_hash);
// Callback function after the DownloadProtectionService completes.
void CheckClientDownloadDone(
int32 download_id,
safe_browsing::DownloadProtectionService::DownloadCheckResult result);
Profile* profile_; Profile* profile_;
scoped_ptr<DownloadPrefs> download_prefs_; scoped_ptr<DownloadPrefs> download_prefs_;
scoped_ptr<DownloadHistory> download_history_; scoped_ptr<DownloadHistory> download_history_;
......
...@@ -170,11 +170,13 @@ class ClientSideDetectionHostTest : public TabContentsWrapperTestHarness { ...@@ -170,11 +170,13 @@ class ClientSideDetectionHostTest : public TabContentsWrapperTestHarness {
} }
virtual void TearDown() { virtual void TearDown() {
// Delete the host object on the UI thread. // Delete the host object on the UI thread and release the
// SafeBrowsingService.
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
new DeleteTask<ClientSideDetectionHost>(csd_host_.release())); new DeleteTask<ClientSideDetectionHost>(csd_host_.release()));
sb_service_ = NULL;
message_loop_.RunAllPending(); message_loop_.RunAllPending();
TabContentsWrapperTestHarness::TearDown(); TabContentsWrapperTestHarness::TearDown();
io_thread_.reset(); io_thread_.reset();
......
...@@ -8,11 +8,15 @@ ...@@ -8,11 +8,15 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h" #include "base/metrics/histogram.h"
#include "base/stl_util.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/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/signature_util.h"
#include "chrome/common/net/http_return.h" #include "chrome/common/net/http_return.h"
#include "chrome/common/safe_browsing/csd.pb.h" #include "chrome/common/safe_browsing/csd.pb.h"
#include "content/browser/browser_thread.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.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
#include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h" #include "net/url_request/url_request_status.h"
...@@ -22,63 +26,110 @@ namespace safe_browsing { ...@@ -22,63 +26,110 @@ namespace safe_browsing {
const char DownloadProtectionService::kDownloadRequestUrl[] = const char DownloadProtectionService::kDownloadRequestUrl[] =
"https://sb-ssl.google.com/safebrowsing/clientreport/download"; "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() DownloadProtectionService::DownloadInfo::DownloadInfo()
: total_bytes(0), user_initiated(false) {} : total_bytes(0), user_initiated(false) {}
DownloadProtectionService::DownloadInfo::~DownloadInfo() {} DownloadProtectionService::DownloadInfo::~DownloadInfo() {}
DownloadProtectionService::DownloadProtectionService( // static
SafeBrowsingService* sb_service, DownloadProtectionService::DownloadInfo
net::URLRequestContextGetter* request_context_getter) DownloadProtectionService::DownloadInfo::FromDownloadItem(
: sb_service_(sb_service), const DownloadItem& item) {
request_context_getter_(request_context_getter), DownloadInfo download_info;
enabled_(false) {} download_info.local_file = item.full_path();
download_info.download_url_chain = item.url_chain();
DownloadProtectionService::~DownloadProtectionService() { download_info.referrer_url = item.referrer_url();
STLDeleteContainerPairFirstPointers(download_requests_.begin(), // TODO(bryner): Fill in the hash (we shouldn't compute it again)
download_requests_.end()); download_info.total_bytes = item.total_bytes();
download_requests_.clear(); // TODO(bryner): Populate user_initiated
return download_info;
} }
void DownloadProtectionService::SetEnabled(bool enabled) { class DownloadProtectionService::CheckClientDownloadRequest
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); : public base::RefCountedThreadSafe<
BrowserThread::PostTask( DownloadProtectionService::CheckClientDownloadRequest,
BrowserThread::IO, BrowserThread::DeleteOnUIThread>,
FROM_HERE, public content::URLFetcherDelegate {
base::Bind(&DownloadProtectionService::SetEnabledOnIOThread, public:
this, enabled)); CheckClientDownloadRequest(const DownloadInfo& info,
} const CheckDownloadCallback& callback,
DownloadProtectionService* service,
void DownloadProtectionService::SetEnabledOnIOThread(bool enabled) { SafeBrowsingService* sb_service)
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); : info_(info),
if (enabled == enabled_) { callback_(callback),
return; service_(service),
sb_service_(sb_service),
pingback_enabled_(service_->enabled()) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
} }
enabled_ = enabled;
if (!enabled_) { void Start() {
for (DownloadRequests::iterator it = download_requests_.begin(); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
it != download_requests_.end(); ++it) { // TODO(noelutz): implement some cache to make sure we don't issue the same
it->second.Run(SAFE); // 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( if (!IsBinaryFile(info_.local_file)) {
const content::URLFetcher* source) { RecordStats(REASON_NOT_BINARY_FILE);
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); PostFinishTask(SAFE);
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);
return; 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; DownloadCheckResultReason reason = REASON_MAX;
reason = REASON_SERVER_PING_FAILED; reason = REASON_SERVER_PING_FAILED;
if (source->GetStatus().is_success() && if (source->GetStatus().is_success() &&
...@@ -92,118 +143,207 @@ void DownloadProtectionService::OnURLFetchComplete( ...@@ -92,118 +143,207 @@ void DownloadProtectionService::OnURLFetchComplete(
reason = REASON_INVALID_RESPONSE_PROTO; reason = REASON_INVALID_RESPONSE_PROTO;
} }
} }
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DownloadProtectionService::EndCheckClientDownload,
this, SAFE, reason, callback));
} else {
NOTREACHED();
}
}
bool DownloadProtectionService::CheckClientDownload( if (reason != REASON_MAX) {
const DownloadInfo& info, RecordStats(reason);
const CheckDownloadCallback& callback) { }
// TODO(noelutz): implement some cache to make sure we don't issue the same // We don't need the fetcher anymore.
// request over and over again if a user downloads the same binary multiple fetcher_.reset();
// times. FinishRequest(SAFE);
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.
} }
// 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( private:
const DownloadInfo& info, friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
const CheckDownloadCallback& callback) { friend class DeleteTask<CheckClientDownloadRequest>;
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!enabled_ || !sb_service_.get()) { virtual ~CheckClientDownloadRequest() {
// This is a hard fail. We won't even call the callback in this case. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RecordStats(REASON_SB_DISABLED);
return;
} }
DownloadCheckResultReason reason = REASON_MAX;
for (size_t i = 0; i < info.download_url_chain.size(); ++i) { void ExtractFileFeatures() {
if (sb_service_->MatchDownloadWhitelistUrl(info.download_url_chain[i])) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
reason = REASON_WHITELISTED_URL; bool is_signed;
break; 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; void CheckWhitelists() {
} DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// TODO(noelutz): check signature and CA against whitelist. DownloadCheckResultReason reason = REASON_MAX;
if (!pingback_enabled_ || !sb_service_.get()) {
ClientDownloadRequest request; reason = REASON_SB_DISABLED;
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 { } 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; void SendRequest() {
if (!request.SerializeToString(&request_data)) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
reason = REASON_INVALID_REQUEST_PROTO;
// 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) { void PostFinishTask(DownloadCheckResult result) {
// We stop here because the download is considered safe.
BrowserThread::PostTask( BrowserThread::PostTask(
BrowserThread::UI, BrowserThread::UI,
FROM_HERE, FROM_HERE,
base::Bind(&DownloadProtectionService::EndCheckClientDownload, base::Bind(&CheckClientDownloadRequest::FinishRequest, this, result));
this, SAFE, reason, callback));
return;
} }
content::URLFetcher* fetcher = content::URLFetcher::Create( void FinishRequest(DownloadCheckResult result) {
0 /* ID used for testing */, GURL(kDownloadRequestUrl), DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::URLFetcher::POST, this); if (service_) {
download_requests_[fetcher] = callback; callback_.Run(result);
fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); service_->RequestFinished(this);
fetcher->SetRequestContext(request_context_getter_.get()); } else {
fetcher->SetUploadData("application/octet-stream", request_data); callback_.Run(SAFE);
fetcher->Start(); }
}
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( void DownloadProtectionService::CheckClientDownload(
DownloadCheckResult result, const DownloadProtectionService::DownloadInfo& info,
DownloadCheckResultReason reason,
const CheckDownloadCallback& callback) { 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)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RecordStats(reason); for (std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
callback.Run(result); download_requests_.begin();
it != download_requests_.end(); ++it) {
(*it)->Cancel();
}
download_requests_.clear();
} }
void DownloadProtectionService::RecordStats(DownloadCheckResultReason reason) { void DownloadProtectionService::RequestFinished(
UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", CheckClientDownloadRequest* request) {
reason, DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
REASON_MAX); std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
download_requests_.find(request);
DCHECK(it != download_requests_.end());
download_requests_.erase(*it);
} }
} // namespace safe_browsing } // namespace safe_browsing
...@@ -9,36 +9,34 @@ ...@@ -9,36 +9,34 @@
#define CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_SERVICE_H_ #define CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_SERVICE_H_
#pragma once #pragma once
#include <map> #include <set>
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/file_path.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/time.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "googleurl/src/gurl.h" #include "googleurl/src/gurl.h"
namespace net { namespace net {
class URLRequestContextGetter; class URLRequestContextGetter;
class URLRequestStatus; class URLRequestStatus;
} // namespace net } // namespace net
class DownloadItem;
class SafeBrowsingService; class SafeBrowsingService;
namespace safe_browsing { namespace safe_browsing {
// This class provides an asynchronous API to check whether a particular // This class provides an asynchronous API to check whether a particular
// client download is malicious or not. // client download is malicious or not.
class DownloadProtectionService class DownloadProtectionService {
: public base::RefCountedThreadSafe<DownloadProtectionService>,
public content::URLFetcherDelegate {
public: public:
// TODO(noelutz): we're missing some fields here: filename to get // TODO(noelutz): we're missing some fields here: server IPs,
// the signature, server IPs, tab URL redirect chain, ... // tab URL redirect chain, ...
struct DownloadInfo { struct DownloadInfo {
FilePath local_file;
std::vector<GURL> download_url_chain; std::vector<GURL> download_url_chain;
GURL referrer_url; GURL referrer_url;
std::string sha256_hash; std::string sha256_hash;
...@@ -46,6 +44,9 @@ class DownloadProtectionService ...@@ -46,6 +44,9 @@ class DownloadProtectionService
bool user_initiated; bool user_initiated;
DownloadInfo(); DownloadInfo();
~DownloadInfo(); ~DownloadInfo();
// Creates a DownloadInfo from a DownloadItem object.
static DownloadInfo FromDownloadItem(const DownloadItem& item);
}; };
enum DownloadCheckResult { enum DownloadCheckResult {
...@@ -59,32 +60,25 @@ class DownloadProtectionService ...@@ -59,32 +60,25 @@ class DownloadProtectionService
typedef base::Callback<void(DownloadCheckResult)> CheckDownloadCallback; typedef base::Callback<void(DownloadCheckResult)> CheckDownloadCallback;
// Creates a download service. The service is initially disabled. You need // Creates a download service. The service is initially disabled. You need
// to call SetEnabled() to start it. We keep scoped references to both of // to call SetEnabled() to start it. |sb_service| owns this object; we
// these objects. // keep a reference to |request_context_getter|.
DownloadProtectionService( DownloadProtectionService(
SafeBrowsingService* sb_service, SafeBrowsingService* sb_service,
net::URLRequestContextGetter* request_context_getter); net::URLRequestContextGetter* request_context_getter);
// From the content::URLFetcherDelegate interface. virtual ~DownloadProtectionService();
virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE;
// Checks whether the given client download is likely to be malicious or not.
// Checks whether the given client download is likely to be // The result is delivered asynchronously via the given callback. This
// malicious or not. If this method returns true it means the // method must be called on the UI thread, and the callback will also be
// download is safe or we're unable to tell whether it is safe or // invoked on the UI thread.
// not. In this case the callback will never be called. If this virtual void CheckClientDownload(const DownloadInfo& info,
// method returns false we will asynchronously check whether the
// download is safe and call the callback when we have the response.
// This method should be called on the UI thread. The callback will
// be called on the UI thread and will always be called asynchronously.
virtual bool CheckClientDownload(const DownloadInfo& info,
const CheckDownloadCallback& callback); const CheckDownloadCallback& callback);
// Enables or disables the service. This is usually called by the // Enables or disables the service. This is usually called by the
// SafeBrowsingService, which tracks whether any profile uses these services // SafeBrowsingService, which tracks whether any profile uses these services
// at all. Disabling cancels any pending requests; existing requests will // at all. Disabling causes any pending and future requests to have their
// have their callbacks called with "SAFE" results. Note: SetEnabled() is // callbacks called with "SAFE" results.
// asynchronous because this method is called on the UI thread but most
// everything else happens on the IO thread.
void SetEnabled(bool enabled); void SetEnabled(bool enabled);
bool enabled() const { bool enabled() const {
...@@ -102,13 +96,12 @@ class DownloadProtectionService ...@@ -102,13 +96,12 @@ class DownloadProtectionService
REASON_INVALID_REQUEST_PROTO, REASON_INVALID_REQUEST_PROTO,
REASON_SERVER_PING_FAILED, REASON_SERVER_PING_FAILED,
REASON_INVALID_RESPONSE_PROTO, REASON_INVALID_RESPONSE_PROTO,
REASON_NOT_BINARY_FILE,
REASON_MAX // Always add new values before this one. REASON_MAX // Always add new values before this one.
}; };
virtual ~DownloadProtectionService();
private: private:
friend class base::RefCountedThreadSafe<DownloadProtectionService>; class CheckClientDownloadRequest; // Per-request state
friend class DownloadProtectionServiceTest; friend class DownloadProtectionServiceTest;
FRIEND_TEST_ALL_PREFIXES(DownloadProtectionServiceTest, FRIEND_TEST_ALL_PREFIXES(DownloadProtectionServiceTest,
CheckClientDownloadValidateRequest); CheckClientDownloadValidateRequest);
...@@ -119,25 +112,20 @@ class DownloadProtectionService ...@@ -119,25 +112,20 @@ class DownloadProtectionService
static const char kDownloadRequestUrl[]; static const char kDownloadRequestUrl[];
// Same as above but this method is called on the IO thread after we have // Cancels all requests in |download_requests_|, and empties it, releasing
// done some basic checks to see whether the download is definitely not // the references to the requests.
// safe. void CancelPendingRequests();
void StartCheckClientDownload(const DownloadInfo& info,
const CheckDownloadCallback& callback);
// This function must run on the UI thread and will invoke the callback
// with the given result.
void EndCheckClientDownload(DownloadCheckResult result,
DownloadCheckResultReason reason,
const CheckDownloadCallback& callback);
void RecordStats(DownloadCheckResultReason reason); // Called by a CheckClientDownloadRequest instance when it finishes, to
// remove it from |download_requests_|.
void RequestFinished(CheckClientDownloadRequest* request);
// SetEnabled(bool) calls this method on the IO thread. static void FillDownloadInfo(const DownloadItem& item,
void SetEnabledOnIOThread(bool enableed); DownloadInfo* download_info);
// This pointer may be NULL if SafeBrowsing is disabled. // This pointer may be NULL if SafeBrowsing is disabled. The
scoped_refptr<SafeBrowsingService> sb_service_; // SafeBrowsingService owns us, so we don't need to hold a reference to it.
SafeBrowsingService* sb_service_;
// The context we use to issue network requests. // The context we use to issue network requests.
scoped_refptr<net::URLRequestContextGetter> request_context_getter_; scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
...@@ -145,9 +133,7 @@ class DownloadProtectionService ...@@ -145,9 +133,7 @@ class DownloadProtectionService
// Map of client download request to the corresponding callback that // Map of client download request to the corresponding callback that
// has to be invoked when the request is done. This map contains all // has to be invoked when the request is done. This map contains all
// pending server requests. // pending server requests.
typedef std::map<const content::URLFetcher*, CheckDownloadCallback> std::set<scoped_refptr<CheckClientDownloadRequest> > download_requests_;
DownloadRequests;
DownloadRequests download_requests_;
// Keeps track of the state of the service. // Keeps track of the state of the service.
bool enabled_; bool enabled_;
......
...@@ -9,12 +9,15 @@ ...@@ -9,12 +9,15 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/file_path.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/message_loop.h" #include "base/message_loop.h"
#include "chrome/common/safe_browsing/csd.pb.h" #include "chrome/common/safe_browsing/csd.pb.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "content/browser/browser_thread.h" #include "content/browser/browser_thread.h"
#include "content/browser/download/download_item.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "content/test/test_url_fetcher_factory.h" #include "content/test/test_url_fetcher_factory.h"
#include "googleurl/src/gurl.h" #include "googleurl/src/gurl.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -41,19 +44,25 @@ class DownloadProtectionServiceTest : public testing::Test { ...@@ -41,19 +44,25 @@ class DownloadProtectionServiceTest : public testing::Test {
protected: protected:
virtual void SetUp() { virtual void SetUp() {
ui_thread_.reset(new BrowserThread(BrowserThread::UI, &msg_loop_)); ui_thread_.reset(new BrowserThread(BrowserThread::UI, &msg_loop_));
io_thread_.reset(new BrowserThread(BrowserThread::IO, &msg_loop_)); // Start real threads for the IO and File threads so that the DCHECKs
// to test that we're on the correct thread work.
io_thread_.reset(new BrowserThread(BrowserThread::IO));
ASSERT_TRUE(io_thread_->Start());
file_thread_.reset(new BrowserThread(BrowserThread::FILE));
ASSERT_TRUE(file_thread_->Start());
sb_service_ = new MockSafeBrowsingService(); sb_service_ = new MockSafeBrowsingService();
download_service_ = new DownloadProtectionService(sb_service_.get(), download_service_ = sb_service_->download_protection_service();
NULL);
download_service_->SetEnabled(true); download_service_->SetEnabled(true);
msg_loop_.RunAllPending(); msg_loop_.RunAllPending();
} }
virtual void TearDown() { virtual void TearDown() {
msg_loop_.RunAllPending(); // Flush all of the thread message loops to ensure that there are no
download_service_ = NULL; // tasks currently running.
FlushThreadMessageLoops();
sb_service_ = NULL; sb_service_ = NULL;
io_thread_.reset(); io_thread_.reset();
file_thread_.reset();
ui_thread_.reset(); ui_thread_.reset();
} }
...@@ -71,6 +80,45 @@ class DownloadProtectionServiceTest : public testing::Test { ...@@ -71,6 +80,45 @@ class DownloadProtectionServiceTest : public testing::Test {
return false; return false;
} }
// Flushes any pending tasks in the message loops of all threads.
void FlushThreadMessageLoops() {
FlushMessageLoop(BrowserThread::FILE);
FlushMessageLoop(BrowserThread::IO);
msg_loop_.RunAllPending();
}
private:
// Helper functions for FlushThreadMessageLoops.
void RunAllPendingAndQuitUI() {
MessageLoop::current()->RunAllPending();
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DownloadProtectionServiceTest::QuitMessageLoop,
base::Unretained(this)));
}
void QuitMessageLoop() {
MessageLoop::current()->Quit();
}
void PostRunMessageLoopTask(BrowserThread::ID thread) {
BrowserThread::PostTask(
thread,
FROM_HERE,
base::Bind(&DownloadProtectionServiceTest::RunAllPendingAndQuitUI,
base::Unretained(this)));
}
void FlushMessageLoop(BrowserThread::ID thread) {
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DownloadProtectionServiceTest::PostRunMessageLoopTask,
base::Unretained(this), thread));
msg_loop_.Run();
}
public: public:
void CheckDoneCallback( void CheckDoneCallback(
DownloadProtectionService::DownloadCheckResult result) { DownloadProtectionService::DownloadCheckResult result) {
...@@ -78,36 +126,51 @@ class DownloadProtectionServiceTest : public testing::Test { ...@@ -78,36 +126,51 @@ class DownloadProtectionServiceTest : public testing::Test {
msg_loop_.Quit(); msg_loop_.Quit();
} }
void SendURLFetchComplete(TestURLFetcher* fetcher) {
fetcher->delegate()->OnURLFetchComplete(fetcher);
}
protected: protected:
scoped_refptr<MockSafeBrowsingService> sb_service_; scoped_refptr<MockSafeBrowsingService> sb_service_;
scoped_refptr<DownloadProtectionService> download_service_; DownloadProtectionService* download_service_;
MessageLoop msg_loop_; MessageLoop msg_loop_;
DownloadProtectionService::DownloadCheckResult result_; DownloadProtectionService::DownloadCheckResult result_;
scoped_ptr<BrowserThread> io_thread_; scoped_ptr<BrowserThread> io_thread_;
scoped_ptr<BrowserThread> file_thread_;
scoped_ptr<BrowserThread> ui_thread_; scoped_ptr<BrowserThread> ui_thread_;
}; };
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadInvalidUrl) { TEST_F(DownloadProtectionServiceTest, CheckClientDownloadInvalidUrl) {
DownloadProtectionService::DownloadInfo info; DownloadProtectionService::DownloadInfo info;
EXPECT_TRUE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_);
// Only http is supported for now. // Only http is supported for now.
info.local_file = FilePath(FILE_PATH_LITERAL("a.exe"));
info.download_url_chain.push_back(GURL("https://www.google.com/")); info.download_url_chain.push_back(GURL("https://www.google.com/"));
EXPECT_TRUE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_);
info.download_url_chain[0] = GURL("ftp://www.google.com/"); info.download_url_chain[0] = GURL("ftp://www.google.com/");
EXPECT_TRUE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_);
} }
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadWhitelistedUrl) { TEST_F(DownloadProtectionServiceTest, CheckClientDownloadWhitelistedUrl) {
DownloadProtectionService::DownloadInfo info; DownloadProtectionService::DownloadInfo info;
info.local_file = FilePath(FILE_PATH_LITERAL("a.exe"));
info.download_url_chain.push_back(GURL("http://www.evil.com/bla.exe")); info.download_url_chain.push_back(GURL("http://www.evil.com/bla.exe"));
info.download_url_chain.push_back(GURL("http://www.google.com/a.exe")); info.download_url_chain.push_back(GURL("http://www.google.com/a.exe"));
info.referrer_url = GURL("http://www.google.com/"); info.referrer_url = GURL("http://www.google.com/");
...@@ -118,20 +181,20 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadWhitelistedUrl) { ...@@ -118,20 +181,20 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadWhitelistedUrl) {
MatchDownloadWhitelistUrl(GURL("http://www.google.com/a.exe"))) MatchDownloadWhitelistUrl(GURL("http://www.google.com/a.exe")))
.WillRepeatedly(Return(true)); .WillRepeatedly(Return(true));
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run(); msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_); EXPECT_EQ(DownloadProtectionService::SAFE, result_);
// Check that the referrer is matched against the whitelist. // Check that the referrer is matched against the whitelist.
info.download_url_chain.pop_back(); info.download_url_chain.pop_back();
info.referrer_url = GURL("http://www.google.com/a.exe"); info.referrer_url = GURL("http://www.google.com/a.exe");
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run(); msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_); EXPECT_EQ(DownloadProtectionService::SAFE, result_);
} }
...@@ -146,12 +209,13 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadFetchFailed) { ...@@ -146,12 +209,13 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadFetchFailed) {
.WillRepeatedly(Return(false)); .WillRepeatedly(Return(false));
DownloadProtectionService::DownloadInfo info; DownloadProtectionService::DownloadInfo info;
info.local_file = FilePath(FILE_PATH_LITERAL("a.exe"));
info.download_url_chain.push_back(GURL("http://www.evil.com/a.exe")); info.download_url_chain.push_back(GURL("http://www.evil.com/a.exe"));
info.referrer_url = GURL("http://www.google.com/"); info.referrer_url = GURL("http://www.google.com/");
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run(); msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_); EXPECT_EQ(DownloadProtectionService::SAFE, result_);
} }
...@@ -166,12 +230,13 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSuccess) { ...@@ -166,12 +230,13 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSuccess) {
.WillRepeatedly(Return(false)); .WillRepeatedly(Return(false));
DownloadProtectionService::DownloadInfo info; DownloadProtectionService::DownloadInfo info;
info.local_file = FilePath(FILE_PATH_LITERAL("a.exe"));
info.download_url_chain.push_back(GURL("http://www.evil.com/a.exe")); info.download_url_chain.push_back(GURL("http://www.evil.com/a.exe"));
info.referrer_url = GURL("http://www.google.com/"); info.referrer_url = GURL("http://www.google.com/");
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run(); msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_); EXPECT_EQ(DownloadProtectionService::SAFE, result_);
...@@ -179,10 +244,10 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSuccess) { ...@@ -179,10 +244,10 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSuccess) {
factory.SetFakeResponse( factory.SetFakeResponse(
DownloadProtectionService::kDownloadRequestUrl, "bla", true); DownloadProtectionService::kDownloadRequestUrl, "bla", true);
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.Run(); msg_loop_.Run();
EXPECT_EQ(DownloadProtectionService::SAFE, result_); EXPECT_EQ(DownloadProtectionService::SAFE, result_);
} }
...@@ -191,6 +256,7 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) { ...@@ -191,6 +256,7 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
TestURLFetcherFactory factory; TestURLFetcherFactory factory;
DownloadProtectionService::DownloadInfo info; DownloadProtectionService::DownloadInfo info;
info.local_file = FilePath(FILE_PATH_LITERAL("bla.exe"));
info.download_url_chain.push_back(GURL("http://www.google.com/")); info.download_url_chain.push_back(GURL("http://www.google.com/"));
info.download_url_chain.push_back(GURL("http://www.google.com/bla.exe")); info.download_url_chain.push_back(GURL("http://www.google.com/bla.exe"));
info.referrer_url = GURL("http://www.google.com/"); info.referrer_url = GURL("http://www.google.com/");
...@@ -200,16 +266,17 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) { ...@@ -200,16 +266,17 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
EXPECT_CALL(*sb_service_, MatchDownloadWhitelistUrl(_)) EXPECT_CALL(*sb_service_, MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false)); .WillRepeatedly(Return(false));
EXPECT_FALSE(download_service_->CheckClientDownload( download_service_->CheckClientDownload(
info, info,
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this)))); base::Unretained(this)));
msg_loop_.RunAllPending(); // Wait until StartCheckClientDownload is called. // Run the message loop(s) until SendRequest is called.
FlushThreadMessageLoops();
ASSERT_TRUE(factory.GetFetcherByID(0)); TestURLFetcher* fetcher = factory.GetFetcherByID(0);
ASSERT_TRUE(fetcher);
ClientDownloadRequest request; ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString( EXPECT_TRUE(request.ParseFromString(fetcher->upload_data()));
factory.GetFetcherByID(0)->upload_data()));
EXPECT_EQ("http://www.google.com/bla.exe", request.url()); EXPECT_EQ("http://www.google.com/bla.exe", request.url());
EXPECT_EQ(info.sha256_hash, request.digests().sha256()); EXPECT_EQ(info.sha256_hash, request.digests().sha256());
EXPECT_EQ(info.total_bytes, request.length()); EXPECT_EQ(info.total_bytes, request.length());
...@@ -222,5 +289,12 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) { ...@@ -222,5 +289,12 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
ClientDownloadRequest::DOWNLOAD_URL, ClientDownloadRequest::DOWNLOAD_URL,
"http://www.google.com/bla.exe", "http://www.google.com/bla.exe",
info.referrer_url.spec())); info.referrer_url.spec()));
// Simulate the request finishing.
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&DownloadProtectionServiceTest::SendURLFetchComplete,
base::Unretained(this), fetcher));
msg_loop_.Run();
} }
} // namespace safe_browsing } // namespace safe_browsing
...@@ -90,6 +90,12 @@ class SafeBrowsingBlockingPageTest : public ChromeRenderViewHostTestHarness, ...@@ -90,6 +90,12 @@ class SafeBrowsingBlockingPageTest : public ChromeRenderViewHostTestHarness,
ResetUserResponse(); ResetUserResponse();
} }
virtual void TearDown() {
// Release the SafeBrowsingService before the BrowserThreads are destroyed.
service_ = NULL;
ChromeRenderViewHostTestHarness::TearDown();
}
// SafeBrowsingService::Client implementation. // SafeBrowsingService::Client implementation.
virtual void OnUrlCheckResult(const GURL& url, virtual void OnUrlCheckResult(const GURL& url,
SafeBrowsingService::UrlCheckResult result) { SafeBrowsingService::UrlCheckResult result) {
......
...@@ -30,7 +30,6 @@ ...@@ -30,7 +30,6 @@
#include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents.h"
#include "content/public/browser/notification_service.h" #include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h" #include "content/public/browser/notification_types.h"
...@@ -175,12 +174,9 @@ SafeBrowsingService::SafeBrowsingService() ...@@ -175,12 +174,9 @@ SafeBrowsingService::SafeBrowsingService()
safe_browsing::ClientSideDetectionService::Create( safe_browsing::ClientSideDetectionService::Create(
g_browser_process->system_request_context())); g_browser_process->system_request_context()));
} }
if (CommandLine::ForCurrentProcess()->HasSwitch( download_service_.reset(new safe_browsing::DownloadProtectionService(
switches::kEnableImprovedDownloadProtection)) { this,
download_service_ = new safe_browsing::DownloadProtectionService( g_browser_process->system_request_context()));
this,
g_browser_process->system_request_context());
}
#endif #endif
} }
...@@ -216,20 +212,12 @@ void SafeBrowsingService::Initialize() { ...@@ -216,20 +212,12 @@ void SafeBrowsingService::Initialize() {
} }
void SafeBrowsingService::ShutDown() { void SafeBrowsingService::ShutDown() {
if (download_service_.get()) {
// Disabling the download service first will ensure that it is
// disabled before the SafeBrowsingService object becomes invalid. The
// download service might stay around for a bit since it's
// ref-counted but it won't do any harm because it will be
// disabled.
download_service_->SetEnabled(false);
download_service_ = NULL;
}
Stop(); Stop();
// The IO thread is going away, so make sure the ClientSideDetectionService // The IO thread is going away, so make sure the ClientSideDetectionService
// dtor executes now since it may call the dtor of URLFetcher which relies // dtor executes now since it may call the dtor of URLFetcher which relies
// on it. // on it.
csd_service_.reset(); csd_service_.reset();
download_service_.reset();
} }
bool SafeBrowsingService::CanCheckUrl(const GURL& url) const { bool SafeBrowsingService::CanCheckUrl(const GURL& url) const {
...@@ -1368,6 +1356,9 @@ void SafeBrowsingService::RefreshState() { ...@@ -1368,6 +1356,9 @@ void SafeBrowsingService::RefreshState() {
if (csd_service_.get()) if (csd_service_.get())
csd_service_->SetEnabledAndRefreshState(enable); csd_service_->SetEnabledAndRefreshState(enable);
if (download_service_.get()) if (download_service_.get()) {
download_service_->SetEnabled(enable); download_service_->SetEnabled(
enable && CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableImprovedDownloadProtection));
}
} }
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "base/task.h" #include "base/task.h"
#include "base/time.h" #include "base/time.h"
#include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h"
#include "content/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
#include "googleurl/src/gurl.h" #include "googleurl/src/gurl.h"
...@@ -49,7 +50,8 @@ class DownloadProtectionService; ...@@ -49,7 +50,8 @@ class DownloadProtectionService;
// Construction needs to happen on the main thread. // Construction needs to happen on the main thread.
class SafeBrowsingService class SafeBrowsingService
: public base::RefCountedThreadSafe<SafeBrowsingService>, : public base::RefCountedThreadSafe<SafeBrowsingService,
BrowserThread::DeleteOnUIThread>,
public content::NotificationObserver { public content::NotificationObserver {
public: public:
class Client; class Client;
...@@ -267,6 +269,8 @@ class SafeBrowsingService ...@@ -267,6 +269,8 @@ class SafeBrowsingService
return csd_service_.get(); return csd_service_.get();
} }
// The DownloadProtectionService is not valid after the SafeBrowsingService
// is destroyed.
safe_browsing::DownloadProtectionService* safe_browsing::DownloadProtectionService*
download_protection_service() const { download_protection_service() const {
return download_service_.get(); return download_service_.get();
...@@ -325,7 +329,8 @@ class SafeBrowsingService ...@@ -325,7 +329,8 @@ class SafeBrowsingService
base::TimeTicks start; // When check was queued. base::TimeTicks start; // When check was queued.
}; };
friend class base::RefCountedThreadSafe<SafeBrowsingService>; friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
friend class DeleteTask<SafeBrowsingService>;
friend class SafeBrowsingServiceTest; friend class SafeBrowsingServiceTest;
// Called to initialize objects that are used on the io_thread. // Called to initialize objects that are used on the io_thread.
...@@ -556,7 +561,7 @@ class SafeBrowsingService ...@@ -556,7 +561,7 @@ class SafeBrowsingService
// The DownloadProtectionService is managed by the SafeBrowsingService, // The DownloadProtectionService is managed by the SafeBrowsingService,
// since its running state and lifecycle depends on SafeBrowsingService's. // since its running state and lifecycle depends on SafeBrowsingService's.
scoped_refptr<safe_browsing::DownloadProtectionService> download_service_; scoped_ptr<safe_browsing::DownloadProtectionService> download_service_;
DISALLOW_COPY_AND_ASSIGN(SafeBrowsingService); DISALLOW_COPY_AND_ASSIGN(SafeBrowsingService);
}; };
......
// Copyright (c) 2011 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.
//
// Utility functions to check executable signatures for malicious binary
// detection. Each platform has its own implementation of this class.
#ifndef CHROME_BROWSER_SAFE_BROWSING_SIGNATURE_UTIL_H_
#define CHROME_BROWSER_SAFE_BROWSING_SIGNATURE_UTIL_H_
#pragma once
class FilePath;
namespace safe_browsing {
namespace signature_util {
// Returns true if the given file path contains a signature. No checks are
// performed as to the validity of the signature.
bool IsSigned(const FilePath& file_path);
} // namespace signature_util
} // namespace safe_browsing
#endif // CHROME_BROWSER_SAFE_BROWSING_SIGNATURE_UTIL_H_
// Copyright (c) 2011 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.
//
// This is a stub for the code signing utilities on Mac and Linux.
// It should eventually be replaced with a real implementation.
#include "chrome/browser/safe_browsing/signature_util.h"
namespace safe_browsing {
namespace signature_util {
bool IsSigned(const FilePath& file_path) {
return false;
}
} // namespace signature_util
} // namespace safe_browsing
// Copyright (c) 2011 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/signature_util.h"
#include <windows.h>
#include <wincrypt.h>
#include "base/file_path.h"
namespace safe_browsing {
namespace signature_util {
bool IsSigned(const FilePath& file_path) {
// Get message handle and store handle from the signed file.
BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
file_path.value().c_str(),
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0, // flags
NULL, // encoding
NULL, // content type
NULL, // format type
NULL, // HCERTSTORE
NULL, // HCRYPTMSG
NULL); // context
return result == TRUE;
}
} // namespace signature_util
} // namespace safe_browsing
// Copyright (c) 2011 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/signature_util.h"
#include "base/base_paths.h"
#include "base/file_path.h"
#include "base/path_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace safe_browsing {
TEST(SignatureUtilWinTest, IsSigned) {
FilePath source_path;
ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &source_path));
FilePath testdata_path = source_path
.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("safe_browsing")
.AppendASCII("download_protection");
EXPECT_TRUE(signature_util::IsSigned(testdata_path.Append(L"signed.exe")));
EXPECT_FALSE(signature_util::IsSigned(
testdata_path.Append(L"unsigned.exe")));
EXPECT_FALSE(signature_util::IsSigned(
testdata_path.Append(L"doesnotexist.exe")));
}
} // namespace safe_browsing
...@@ -2055,6 +2055,9 @@ ...@@ -2055,6 +2055,9 @@
'browser/safe_browsing/safe_browsing_store_file.h', 'browser/safe_browsing/safe_browsing_store_file.h',
'browser/safe_browsing/safe_browsing_util.cc', 'browser/safe_browsing/safe_browsing_util.cc',
'browser/safe_browsing/safe_browsing_util.h', 'browser/safe_browsing/safe_browsing_util.h',
'browser/safe_browsing/signature_util_posix.cc',
'browser/safe_browsing/signature_util_win.cc',
'browser/safe_browsing/signature_util.h',
'browser/screensaver_window_finder_gtk.cc', 'browser/screensaver_window_finder_gtk.cc',
'browser/screensaver_window_finder_gtk.h', 'browser/screensaver_window_finder_gtk.h',
'browser/search_engines/search_engine_type.h', 'browser/search_engines/search_engine_type.h',
......
...@@ -1508,6 +1508,7 @@ ...@@ -1508,6 +1508,7 @@
'browser/safe_browsing/safe_browsing_store_unittest.cc', 'browser/safe_browsing/safe_browsing_store_unittest.cc',
'browser/safe_browsing/safe_browsing_store_unittest_helper.cc', 'browser/safe_browsing/safe_browsing_store_unittest_helper.cc',
'browser/safe_browsing/safe_browsing_util_unittest.cc', 'browser/safe_browsing/safe_browsing_util_unittest.cc',
'browser/safe_browsing/signature_util_win_unittest.cc',
'browser/search_engines/search_host_to_urls_map_unittest.cc', 'browser/search_engines/search_host_to_urls_map_unittest.cc',
'browser/search_engines/search_provider_install_data_unittest.cc', 'browser/search_engines/search_provider_install_data_unittest.cc',
'browser/search_engines/template_url_fetcher_unittest.cc', 'browser/search_engines/template_url_fetcher_unittest.cc',
...@@ -1989,6 +1990,7 @@ ...@@ -1989,6 +1990,7 @@
'sources/': [ 'sources/': [
['exclude', '^browser/password_manager/native_backend_gnome_x_unittest.cc'], ['exclude', '^browser/password_manager/native_backend_gnome_x_unittest.cc'],
['exclude', '^browser/password_manager/native_backend_kwallet_x_unittest.cc'], ['exclude', '^browser/password_manager/native_backend_kwallet_x_unittest.cc'],
['exclude', '^browser/safe_browsing/download_protection_service_unittest.cc' ],
['exclude', '^../content/browser/geolocation/wifi_data_provider_linux_unittest.cc'], ['exclude', '^../content/browser/geolocation/wifi_data_provider_linux_unittest.cc'],
# TODO(thestig) Enable PrintPreviewUI tests on CrOS when # TODO(thestig) Enable PrintPreviewUI tests on CrOS when
# print preview is enabled on CrOS. # print preview is enabled on CrOS.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment