diff options
Diffstat (limited to 'chrome/browser/safe_browsing/download_protection_service.cc')
-rw-r--r-- | chrome/browser/safe_browsing/download_protection_service.cc | 412 |
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 |