1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/safe_browsing/download_protection_service.h"
6
7 #include "base/bind.h"
8 #include "base/compiler_specific.h"
9 #include "base/format_macros.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/sequenced_task_runner_helpers.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/sequenced_worker_pool.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/history/history_service.h"
22 #include "chrome/browser/history/history_service_factory.h"
23 #include "chrome/browser/safe_browsing/binary_feature_extractor.h"
24 #include "chrome/browser/safe_browsing/download_feedback_service.h"
25 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
26 #include "chrome/browser/safe_browsing/sandboxed_zip_analyzer.h"
27 #include "chrome/browser/ui/browser.h"
28 #include "chrome/browser/ui/browser_list.h"
29 #include "chrome/common/safe_browsing/csd.pb.h"
30 #include "chrome/common/safe_browsing/download_protection_util.h"
31 #include "chrome/common/safe_browsing/zip_analyzer.h"
32 #include "chrome/common/url_constants.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/download_item.h"
35 #include "content/public/browser/page_navigator.h"
36 #include "google_apis/google_api_keys.h"
37 #include "net/base/escape.h"
38 #include "net/base/load_flags.h"
39 #include "net/cert/x509_cert_types.h"
40 #include "net/cert/x509_certificate.h"
41 #include "net/http/http_status_code.h"
42 #include "net/url_request/url_fetcher.h"
43 #include "net/url_request/url_fetcher_delegate.h"
44 #include "net/url_request/url_request_context_getter.h"
45 #include "net/url_request/url_request_status.h"
46
47 using content::BrowserThread;
48
49 namespace {
50 static const int64 kDownloadRequestTimeoutMs = 7000;
51 } // namespace
52
53 namespace safe_browsing {
54
55 const char DownloadProtectionService::kDownloadRequestUrl[] =
56 "https://sb-ssl.google.com/safebrowsing/clientreport/download";
57
58 namespace {
59 // List of extensions for which we track some UMA stats.
60 enum MaliciousExtensionType {
61 EXTENSION_EXE,
62 EXTENSION_MSI,
63 EXTENSION_CAB,
64 EXTENSION_SYS,
65 EXTENSION_SCR,
66 EXTENSION_DRV,
67 EXTENSION_BAT,
68 EXTENSION_ZIP,
69 EXTENSION_RAR,
70 EXTENSION_DLL,
71 EXTENSION_PIF,
72 EXTENSION_COM,
73 EXTENSION_JAR,
74 EXTENSION_CLASS,
75 EXTENSION_PDF,
76 EXTENSION_VB,
77 EXTENSION_REG,
78 EXTENSION_GRP,
79 EXTENSION_OTHER, // Groups all other extensions into one bucket.
80 EXTENSION_CRX,
81 EXTENSION_APK,
82 EXTENSION_DMG,
83 EXTENSION_PKG,
84 EXTENSION_TORRENT,
85 EXTENSION_MAX,
86 };
87
GetExtensionType(const base::FilePath & f)88 MaliciousExtensionType GetExtensionType(const base::FilePath& f) {
89 if (f.MatchesExtension(FILE_PATH_LITERAL(".exe"))) return EXTENSION_EXE;
90 if (f.MatchesExtension(FILE_PATH_LITERAL(".msi"))) return EXTENSION_MSI;
91 if (f.MatchesExtension(FILE_PATH_LITERAL(".cab"))) return EXTENSION_CAB;
92 if (f.MatchesExtension(FILE_PATH_LITERAL(".sys"))) return EXTENSION_SYS;
93 if (f.MatchesExtension(FILE_PATH_LITERAL(".scr"))) return EXTENSION_SCR;
94 if (f.MatchesExtension(FILE_PATH_LITERAL(".drv"))) return EXTENSION_DRV;
95 if (f.MatchesExtension(FILE_PATH_LITERAL(".bat"))) return EXTENSION_BAT;
96 if (f.MatchesExtension(FILE_PATH_LITERAL(".zip"))) return EXTENSION_ZIP;
97 if (f.MatchesExtension(FILE_PATH_LITERAL(".rar"))) return EXTENSION_RAR;
98 if (f.MatchesExtension(FILE_PATH_LITERAL(".dll"))) return EXTENSION_DLL;
99 if (f.MatchesExtension(FILE_PATH_LITERAL(".pif"))) return EXTENSION_PIF;
100 if (f.MatchesExtension(FILE_PATH_LITERAL(".com"))) return EXTENSION_COM;
101 if (f.MatchesExtension(FILE_PATH_LITERAL(".jar"))) return EXTENSION_JAR;
102 if (f.MatchesExtension(FILE_PATH_LITERAL(".class"))) return EXTENSION_CLASS;
103 if (f.MatchesExtension(FILE_PATH_LITERAL(".pdf"))) return EXTENSION_PDF;
104 if (f.MatchesExtension(FILE_PATH_LITERAL(".vb"))) return EXTENSION_VB;
105 if (f.MatchesExtension(FILE_PATH_LITERAL(".reg"))) return EXTENSION_REG;
106 if (f.MatchesExtension(FILE_PATH_LITERAL(".grp"))) return EXTENSION_GRP;
107 if (f.MatchesExtension(FILE_PATH_LITERAL(".crx"))) return EXTENSION_CRX;
108 if (f.MatchesExtension(FILE_PATH_LITERAL(".apk"))) return EXTENSION_APK;
109 if (f.MatchesExtension(FILE_PATH_LITERAL(".dmg"))) return EXTENSION_DMG;
110 if (f.MatchesExtension(FILE_PATH_LITERAL(".pkg"))) return EXTENSION_PKG;
111 if (f.MatchesExtension(FILE_PATH_LITERAL(".torrent")))
112 return EXTENSION_TORRENT;
113 return EXTENSION_OTHER;
114 }
115
RecordFileExtensionType(const base::FilePath & file)116 void RecordFileExtensionType(const base::FilePath& file) {
117 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.DownloadExtensions",
118 GetExtensionType(file),
119 EXTENSION_MAX);
120 }
121
122 // Enumerate for histogramming purposes.
123 // DO NOT CHANGE THE ORDERING OF THESE VALUES (different histogram data will
124 // be mixed together based on their values).
125 enum SBStatsType {
126 DOWNLOAD_URL_CHECKS_TOTAL,
127 DOWNLOAD_URL_CHECKS_CANCELED,
128 DOWNLOAD_URL_CHECKS_MALWARE,
129
130 DOWNLOAD_HASH_CHECKS_TOTAL,
131 DOWNLOAD_HASH_CHECKS_MALWARE,
132
133 // Memory space for histograms is determined by the max.
134 // ALWAYS ADD NEW VALUES BEFORE THIS ONE.
135 DOWNLOAD_CHECKS_MAX
136 };
137 } // namespace
138
139 // Parent SafeBrowsing::Client class used to lookup the bad binary
140 // URL and digest list. There are two sub-classes (one for each list).
141 class DownloadSBClient
142 : public SafeBrowsingDatabaseManager::Client,
143 public base::RefCountedThreadSafe<DownloadSBClient> {
144 public:
DownloadSBClient(const content::DownloadItem & item,const DownloadProtectionService::CheckDownloadCallback & callback,const scoped_refptr<SafeBrowsingUIManager> & ui_manager,SBStatsType total_type,SBStatsType dangerous_type)145 DownloadSBClient(
146 const content::DownloadItem& item,
147 const DownloadProtectionService::CheckDownloadCallback& callback,
148 const scoped_refptr<SafeBrowsingUIManager>& ui_manager,
149 SBStatsType total_type,
150 SBStatsType dangerous_type)
151 : sha256_hash_(item.GetHash()),
152 url_chain_(item.GetUrlChain()),
153 referrer_url_(item.GetReferrerUrl()),
154 callback_(callback),
155 ui_manager_(ui_manager),
156 start_time_(base::TimeTicks::Now()),
157 total_type_(total_type),
158 dangerous_type_(dangerous_type) {}
159
160 virtual void StartCheck() = 0;
161 virtual bool IsDangerous(SBThreatType threat_type) const = 0;
162
163 protected:
164 friend class base::RefCountedThreadSafe<DownloadSBClient>;
~DownloadSBClient()165 virtual ~DownloadSBClient() {}
166
CheckDone(SBThreatType threat_type)167 void CheckDone(SBThreatType threat_type) {
168 DownloadProtectionService::DownloadCheckResult result =
169 IsDangerous(threat_type) ?
170 DownloadProtectionService::DANGEROUS :
171 DownloadProtectionService::SAFE;
172 BrowserThread::PostTask(BrowserThread::UI,
173 FROM_HERE,
174 base::Bind(callback_, result));
175 UpdateDownloadCheckStats(total_type_);
176 if (threat_type != SB_THREAT_TYPE_SAFE) {
177 UpdateDownloadCheckStats(dangerous_type_);
178 BrowserThread::PostTask(
179 BrowserThread::UI,
180 FROM_HERE,
181 base::Bind(&DownloadSBClient::ReportMalware,
182 this, threat_type));
183 }
184 }
185
ReportMalware(SBThreatType threat_type)186 void ReportMalware(SBThreatType threat_type) {
187 std::string post_data;
188 if (!sha256_hash_.empty())
189 post_data += base::HexEncode(sha256_hash_.data(),
190 sha256_hash_.size()) + "\n";
191 for (size_t i = 0; i < url_chain_.size(); ++i) {
192 post_data += url_chain_[i].spec() + "\n";
193 }
194 ui_manager_->ReportSafeBrowsingHit(
195 url_chain_.back(), // malicious_url
196 url_chain_.front(), // page_url
197 referrer_url_,
198 true, // is_subresource
199 threat_type,
200 post_data);
201 }
202
UpdateDownloadCheckStats(SBStatsType stat_type)203 void UpdateDownloadCheckStats(SBStatsType stat_type) {
204 UMA_HISTOGRAM_ENUMERATION("SB2.DownloadChecks",
205 stat_type,
206 DOWNLOAD_CHECKS_MAX);
207 }
208
209 std::string sha256_hash_;
210 std::vector<GURL> url_chain_;
211 GURL referrer_url_;
212 DownloadProtectionService::CheckDownloadCallback callback_;
213 scoped_refptr<SafeBrowsingUIManager> ui_manager_;
214 base::TimeTicks start_time_;
215
216 private:
217 const SBStatsType total_type_;
218 const SBStatsType dangerous_type_;
219
220 DISALLOW_COPY_AND_ASSIGN(DownloadSBClient);
221 };
222
223 class DownloadUrlSBClient : public DownloadSBClient {
224 public:
DownloadUrlSBClient(const content::DownloadItem & item,const DownloadProtectionService::CheckDownloadCallback & callback,const scoped_refptr<SafeBrowsingUIManager> & ui_manager,const scoped_refptr<SafeBrowsingDatabaseManager> & database_manager)225 DownloadUrlSBClient(
226 const content::DownloadItem& item,
227 const DownloadProtectionService::CheckDownloadCallback& callback,
228 const scoped_refptr<SafeBrowsingUIManager>& ui_manager,
229 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager)
230 : DownloadSBClient(item, callback, ui_manager,
231 DOWNLOAD_URL_CHECKS_TOTAL,
232 DOWNLOAD_URL_CHECKS_MALWARE),
233 database_manager_(database_manager) { }
234
StartCheck()235 virtual void StartCheck() OVERRIDE {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
237 if (!database_manager_.get() ||
238 database_manager_->CheckDownloadUrl(url_chain_, this)) {
239 CheckDone(SB_THREAT_TYPE_SAFE);
240 } else {
241 AddRef(); // SafeBrowsingService takes a pointer not a scoped_refptr.
242 }
243 }
244
IsDangerous(SBThreatType threat_type) const245 virtual bool IsDangerous(SBThreatType threat_type) const OVERRIDE {
246 return threat_type == SB_THREAT_TYPE_BINARY_MALWARE_URL;
247 }
248
OnCheckDownloadUrlResult(const std::vector<GURL> & url_chain,SBThreatType threat_type)249 virtual void OnCheckDownloadUrlResult(const std::vector<GURL>& url_chain,
250 SBThreatType threat_type) OVERRIDE {
251 CheckDone(threat_type);
252 UMA_HISTOGRAM_TIMES("SB2.DownloadUrlCheckDuration",
253 base::TimeTicks::Now() - start_time_);
254 Release();
255 }
256
257 protected:
~DownloadUrlSBClient()258 virtual ~DownloadUrlSBClient() {}
259
260 private:
261 scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
262
263 DISALLOW_COPY_AND_ASSIGN(DownloadUrlSBClient);
264 };
265
266 class DownloadProtectionService::CheckClientDownloadRequest
267 : public base::RefCountedThreadSafe<
268 DownloadProtectionService::CheckClientDownloadRequest,
269 BrowserThread::DeleteOnUIThread>,
270 public net::URLFetcherDelegate,
271 public content::DownloadItem::Observer {
272 public:
CheckClientDownloadRequest(content::DownloadItem * item,const CheckDownloadCallback & callback,DownloadProtectionService * service,const scoped_refptr<SafeBrowsingDatabaseManager> & database_manager,BinaryFeatureExtractor * binary_feature_extractor)273 CheckClientDownloadRequest(
274 content::DownloadItem* item,
275 const CheckDownloadCallback& callback,
276 DownloadProtectionService* service,
277 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager,
278 BinaryFeatureExtractor* binary_feature_extractor)
279 : item_(item),
280 url_chain_(item->GetUrlChain()),
281 referrer_url_(item->GetReferrerUrl()),
282 tab_url_(item->GetTabUrl()),
283 tab_referrer_url_(item->GetTabReferrerUrl()),
284 zipped_executable_(false),
285 callback_(callback),
286 service_(service),
287 binary_feature_extractor_(binary_feature_extractor),
288 database_manager_(database_manager),
289 pingback_enabled_(service_->enabled()),
290 finished_(false),
291 type_(ClientDownloadRequest::WIN_EXECUTABLE),
292 weakptr_factory_(this),
293 start_time_(base::TimeTicks::Now()) {
294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
295 item_->AddObserver(this);
296 }
297
Start()298 void Start() {
299 VLOG(2) << "Starting SafeBrowsing download check for: "
300 << item_->DebugString(true);
301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
302 // TODO(noelutz): implement some cache to make sure we don't issue the same
303 // request over and over again if a user downloads the same binary multiple
304 // times.
305 DownloadCheckResultReason reason = REASON_MAX;
306 if (!IsSupportedDownload(
307 *item_, item_->GetTargetFilePath(), &reason, &type_)) {
308 switch (reason) {
309 case REASON_EMPTY_URL_CHAIN:
310 case REASON_INVALID_URL:
311 PostFinishTask(SAFE, reason);
312 return;
313
314 case REASON_NOT_BINARY_FILE:
315 RecordFileExtensionType(item_->GetTargetFilePath());
316 PostFinishTask(SAFE, reason);
317 return;
318
319 default:
320 // We only expect the reasons explicitly handled above.
321 NOTREACHED();
322 }
323 }
324 RecordFileExtensionType(item_->GetTargetFilePath());
325
326 // Compute features from the file contents. Note that we record histograms
327 // based on the result, so this runs regardless of whether the pingbacks
328 // are enabled.
329 if (item_->GetTargetFilePath().MatchesExtension(
330 FILE_PATH_LITERAL(".zip"))) {
331 StartExtractZipFeatures();
332 } else {
333 DCHECK(!download_protection_util::IsArchiveFile(
334 item_->GetTargetFilePath()));
335 StartExtractFileFeatures();
336 }
337 }
338
339 // Start a timeout to cancel the request if it takes too long.
340 // This should only be called after we have finished accessing the file.
StartTimeout()341 void StartTimeout() {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 if (!service_) {
344 // Request has already been cancelled.
345 return;
346 }
347 timeout_start_time_ = base::TimeTicks::Now();
348 BrowserThread::PostDelayedTask(
349 BrowserThread::UI,
350 FROM_HERE,
351 base::Bind(&CheckClientDownloadRequest::Cancel,
352 weakptr_factory_.GetWeakPtr()),
353 base::TimeDelta::FromMilliseconds(
354 service_->download_request_timeout_ms()));
355 }
356
357 // Canceling a request will cause us to always report the result as SAFE
358 // unless a pending request is about to call FinishRequest.
Cancel()359 void Cancel() {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
361 if (fetcher_.get()) {
362 // The DownloadProtectionService is going to release its reference, so we
363 // might be destroyed before the URLFetcher completes. Cancel the
364 // fetcher so it does not try to invoke OnURLFetchComplete.
365 fetcher_.reset();
366 }
367 // Note: If there is no fetcher, then some callback is still holding a
368 // reference to this object. We'll eventually wind up in some method on
369 // the UI thread that will call FinishRequest() again. If FinishRequest()
370 // is called a second time, it will be a no-op.
371 FinishRequest(SAFE, REASON_REQUEST_CANCELED);
372 // Calling FinishRequest might delete this object, we may be deleted by
373 // this point.
374 }
375
376 // content::DownloadItem::Observer implementation.
OnDownloadDestroyed(content::DownloadItem * download)377 virtual void OnDownloadDestroyed(content::DownloadItem* download) OVERRIDE {
378 Cancel();
379 DCHECK(item_ == NULL);
380 }
381
382 // From the net::URLFetcherDelegate interface.
OnURLFetchComplete(const net::URLFetcher * source)383 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
384 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
385 DCHECK_EQ(source, fetcher_.get());
386 VLOG(2) << "Received a response for URL: "
387 << item_->GetUrlChain().back() << ": success="
388 << source->GetStatus().is_success() << " response_code="
389 << source->GetResponseCode();
390 if (source->GetStatus().is_success()) {
391 UMA_HISTOGRAM_SPARSE_SLOWLY(
392 "SBClientDownload.DownloadRequestResponseCode",
393 source->GetResponseCode());
394 }
395 UMA_HISTOGRAM_SPARSE_SLOWLY(
396 "SBClientDownload.DownloadRequestNetError",
397 -source->GetStatus().error());
398 DownloadCheckResultReason reason = REASON_SERVER_PING_FAILED;
399 DownloadCheckResult result = SAFE;
400 if (source->GetStatus().is_success() &&
401 net::HTTP_OK == source->GetResponseCode()) {
402 ClientDownloadResponse response;
403 std::string data;
404 bool got_data = source->GetResponseAsString(&data);
405 DCHECK(got_data);
406 if (!response.ParseFromString(data)) {
407 reason = REASON_INVALID_RESPONSE_PROTO;
408 } else if (response.verdict() == ClientDownloadResponse::SAFE) {
409 reason = REASON_DOWNLOAD_SAFE;
410 } else if (service_ && !service_->IsSupportedDownload(
411 *item_, item_->GetTargetFilePath())) {
412 // The client of the download protection service assumes that we don't
413 // support this download so we cannot return any other verdict than
414 // SAFE even if the server says it's dangerous to download this file.
415 // Note: if service_ is NULL we already cancelled the request and
416 // returned SAFE.
417 reason = REASON_DOWNLOAD_NOT_SUPPORTED;
418 } else if (response.verdict() == ClientDownloadResponse::DANGEROUS) {
419 reason = REASON_DOWNLOAD_DANGEROUS;
420 result = DANGEROUS;
421 } else if (response.verdict() == ClientDownloadResponse::UNCOMMON) {
422 reason = REASON_DOWNLOAD_UNCOMMON;
423 result = UNCOMMON;
424 } else if (response.verdict() == ClientDownloadResponse::DANGEROUS_HOST) {
425 reason = REASON_DOWNLOAD_DANGEROUS_HOST;
426 result = DANGEROUS_HOST;
427 } else if (
428 response.verdict() == ClientDownloadResponse::POTENTIALLY_UNWANTED) {
429 reason = REASON_DOWNLOAD_POTENTIALLY_UNWANTED;
430 result = POTENTIALLY_UNWANTED;
431 } else {
432 LOG(DFATAL) << "Unknown download response verdict: "
433 << response.verdict();
434 reason = REASON_INVALID_RESPONSE_VERDICT;
435 }
436 DownloadFeedbackService::MaybeStorePingsForDownload(
437 result, item_, client_download_request_data_, data);
438 }
439 // We don't need the fetcher anymore.
440 fetcher_.reset();
441 UMA_HISTOGRAM_TIMES("SBClientDownload.DownloadRequestDuration",
442 base::TimeTicks::Now() - start_time_);
443 UMA_HISTOGRAM_TIMES("SBClientDownload.DownloadRequestNetworkDuration",
444 base::TimeTicks::Now() - request_start_time_);
445 FinishRequest(result, reason);
446 }
447
IsSupportedDownload(const content::DownloadItem & item,const base::FilePath & target_path,DownloadCheckResultReason * reason,ClientDownloadRequest::DownloadType * type)448 static bool IsSupportedDownload(const content::DownloadItem& item,
449 const base::FilePath& target_path,
450 DownloadCheckResultReason* reason,
451 ClientDownloadRequest::DownloadType* type) {
452 if (item.GetUrlChain().empty()) {
453 *reason = REASON_EMPTY_URL_CHAIN;
454 return false;
455 }
456 const GURL& final_url = item.GetUrlChain().back();
457 if (!final_url.is_valid() || final_url.is_empty() ||
458 !final_url.IsStandard() || final_url.SchemeIsFile()) {
459 *reason = REASON_INVALID_URL;
460 return false;
461 }
462 if (!download_protection_util::IsBinaryFile(target_path)) {
463 *reason = REASON_NOT_BINARY_FILE;
464 return false;
465 }
466 *type = download_protection_util::GetDownloadType(target_path);
467 return true;
468 }
469
470 private:
471 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
472 friend class base::DeleteHelper<CheckClientDownloadRequest>;
473
~CheckClientDownloadRequest()474 virtual ~CheckClientDownloadRequest() {
475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
476 DCHECK(item_ == NULL);
477 }
478
OnFileFeatureExtractionDone()479 void OnFileFeatureExtractionDone() {
480 // This can run in any thread, since it just posts more messages.
481
482 // TODO(noelutz): DownloadInfo should also contain the IP address of
483 // every URL in the redirect chain. We also should check whether the
484 // download URL is hosted on the internal network.
485 BrowserThread::PostTask(
486 BrowserThread::IO,
487 FROM_HERE,
488 base::Bind(&CheckClientDownloadRequest::CheckWhitelists, this));
489
490 // We wait until after the file checks finish to start the timeout, as
491 // windows can cause permissions errors if the timeout fired while we were
492 // checking the file signature and we tried to complete the download.
493 BrowserThread::PostTask(
494 BrowserThread::UI,
495 FROM_HERE,
496 base::Bind(&CheckClientDownloadRequest::StartTimeout, this));
497 }
498
StartExtractFileFeatures()499 void StartExtractFileFeatures() {
500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501 DCHECK(item_); // Called directly from Start(), item should still exist.
502 // Since we do blocking I/O, offload this to a worker thread.
503 // The task does not need to block shutdown.
504 BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
505 FROM_HERE,
506 base::Bind(&CheckClientDownloadRequest::ExtractFileFeatures,
507 this, item_->GetFullPath()),
508 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
509 }
510
ExtractFileFeatures(const base::FilePath & file_path)511 void ExtractFileFeatures(const base::FilePath& file_path) {
512 base::TimeTicks start_time = base::TimeTicks::Now();
513 binary_feature_extractor_->CheckSignature(file_path, &signature_info_);
514 bool is_signed = (signature_info_.certificate_chain_size() > 0);
515 if (is_signed) {
516 VLOG(2) << "Downloaded a signed binary: " << file_path.value();
517 } else {
518 VLOG(2) << "Downloaded an unsigned binary: "
519 << file_path.value();
520 }
521 UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed);
522 UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractSignatureFeaturesTime",
523 base::TimeTicks::Now() - start_time);
524
525 start_time = base::TimeTicks::Now();
526 binary_feature_extractor_->ExtractImageHeaders(file_path, &image_headers_);
527 UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractImageHeadersTime",
528 base::TimeTicks::Now() - start_time);
529
530 OnFileFeatureExtractionDone();
531 }
532
StartExtractZipFeatures()533 void StartExtractZipFeatures() {
534 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
535 DCHECK(item_); // Called directly from Start(), item should still exist.
536 zip_analysis_start_time_ = base::TimeTicks::Now();
537 // We give the zip analyzer a weak pointer to this object. Since the
538 // analyzer is refcounted, it might outlive the request.
539 analyzer_ = new SandboxedZipAnalyzer(
540 item_->GetFullPath(),
541 base::Bind(&CheckClientDownloadRequest::OnZipAnalysisFinished,
542 weakptr_factory_.GetWeakPtr()));
543 analyzer_->Start();
544 }
545
OnZipAnalysisFinished(const zip_analyzer::Results & results)546 void OnZipAnalysisFinished(const zip_analyzer::Results& results) {
547 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
548 if (!service_)
549 return;
550 if (results.success) {
551 zipped_executable_ = results.has_executable;
552 VLOG(1) << "Zip analysis finished for " << item_->GetFullPath().value()
553 << ", has_executable=" << results.has_executable
554 << " has_archive=" << results.has_archive;
555 } else {
556 VLOG(1) << "Zip analysis failed for " << item_->GetFullPath().value();
557 }
558 UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasExecutable",
559 zipped_executable_);
560 UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasArchiveButNoExecutable",
561 results.has_archive && !zipped_executable_);
562 UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractZipFeaturesTime",
563 base::TimeTicks::Now() - zip_analysis_start_time_);
564
565 if (!zipped_executable_) {
566 PostFinishTask(SAFE, REASON_ARCHIVE_WITHOUT_BINARIES);
567 return;
568 }
569 OnFileFeatureExtractionDone();
570 }
571
CheckWhitelists()572 void CheckWhitelists() {
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
574 DownloadCheckResultReason reason = REASON_MAX;
575 if (!database_manager_.get()) {
576 reason = REASON_SB_DISABLED;
577 } else {
578 const GURL& url = url_chain_.back();
579 if (url.is_valid() && database_manager_->MatchDownloadWhitelistUrl(url)) {
580 VLOG(2) << url << " is on the download whitelist.";
581 reason = REASON_WHITELISTED_URL;
582 }
583 if (reason != REASON_MAX || signature_info_.trusted()) {
584 UMA_HISTOGRAM_COUNTS("SBClientDownload.SignedOrWhitelistedDownload", 1);
585 }
586 }
587 if (reason == REASON_MAX && signature_info_.trusted()) {
588 for (int i = 0; i < signature_info_.certificate_chain_size(); ++i) {
589 if (CertificateChainIsWhitelisted(
590 signature_info_.certificate_chain(i))) {
591 reason = REASON_TRUSTED_EXECUTABLE;
592 break;
593 }
594 }
595 }
596 if (reason != REASON_MAX) {
597 PostFinishTask(SAFE, reason);
598 } else if (!pingback_enabled_) {
599 PostFinishTask(SAFE, REASON_PING_DISABLED);
600 } else {
601 // Currently, the UI only works on Windows so we don't even bother
602 // with pinging the server if we're not on Windows. TODO(noelutz):
603 // change this code once the UI is done for Linux and Mac.
604 #if defined(OS_WIN)
605 // The URLFetcher is owned by the UI thread, so post a message to
606 // start the pingback.
607 BrowserThread::PostTask(
608 BrowserThread::UI,
609 FROM_HERE,
610 base::Bind(&CheckClientDownloadRequest::GetTabRedirects, this));
611 #else
612 PostFinishTask(SAFE, REASON_OS_NOT_SUPPORTED);
613 #endif
614 }
615 }
616
GetTabRedirects()617 void GetTabRedirects() {
618 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
619 if (!tab_url_.is_valid()) {
620 SendRequest();
621 return;
622 }
623
624 Profile* profile = Profile::FromBrowserContext(item_->GetBrowserContext());
625 HistoryService* history =
626 HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
627 if (!history) {
628 SendRequest();
629 return;
630 }
631
632 history->QueryRedirectsTo(
633 tab_url_,
634 &request_consumer_,
635 base::Bind(&CheckClientDownloadRequest::OnGotTabRedirects,
636 base::Unretained(this)));
637 }
638
OnGotTabRedirects(HistoryService::Handle handle,GURL url,bool success,history::RedirectList * redirect_list)639 void OnGotTabRedirects(HistoryService::Handle handle,
640 GURL url,
641 bool success,
642 history::RedirectList* redirect_list) {
643 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
644 DCHECK_EQ(url, tab_url_);
645
646 if (success && redirect_list->size() > 0) {
647 for (history::RedirectList::reverse_iterator i = redirect_list->rbegin();
648 i != redirect_list->rend();
649 ++i) {
650 tab_redirects_.push_back(*i);
651 }
652 }
653
654 SendRequest();
655 }
656
SendRequest()657 void SendRequest() {
658 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
659
660 // This is our last chance to check whether the request has been canceled
661 // before sending it.
662 if (!service_)
663 return;
664
665 ClientDownloadRequest request;
666 request.set_url(item_->GetUrlChain().back().spec());
667 request.mutable_digests()->set_sha256(item_->GetHash());
668 request.set_length(item_->GetReceivedBytes());
669 for (size_t i = 0; i < item_->GetUrlChain().size(); ++i) {
670 ClientDownloadRequest::Resource* resource = request.add_resources();
671 resource->set_url(item_->GetUrlChain()[i].spec());
672 if (i == item_->GetUrlChain().size() - 1) {
673 // The last URL in the chain is the download URL.
674 resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
675 resource->set_referrer(item_->GetReferrerUrl().spec());
676 DVLOG(2) << "dl url " << resource->url();
677 if (!item_->GetRemoteAddress().empty()) {
678 resource->set_remote_ip(item_->GetRemoteAddress());
679 DVLOG(2) << " dl url remote addr: " << resource->remote_ip();
680 }
681 DVLOG(2) << "dl referrer " << resource->referrer();
682 } else {
683 DVLOG(2) << "dl redirect " << i << " " << resource->url();
684 resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
685 }
686 // TODO(noelutz): fill out the remote IP addresses.
687 }
688 // TODO(mattm): fill out the remote IP addresses for tab resources.
689 for (size_t i = 0; i < tab_redirects_.size(); ++i) {
690 ClientDownloadRequest::Resource* resource = request.add_resources();
691 DVLOG(2) << "tab redirect " << i << " " << tab_redirects_[i].spec();
692 resource->set_url(tab_redirects_[i].spec());
693 resource->set_type(ClientDownloadRequest::TAB_REDIRECT);
694 }
695 if (tab_url_.is_valid()) {
696 ClientDownloadRequest::Resource* resource = request.add_resources();
697 resource->set_url(tab_url_.spec());
698 DVLOG(2) << "tab url " << resource->url();
699 resource->set_type(ClientDownloadRequest::TAB_URL);
700 if (tab_referrer_url_.is_valid()) {
701 resource->set_referrer(tab_referrer_url_.spec());
702 DVLOG(2) << "tab referrer " << resource->referrer();
703 }
704 }
705
706 request.set_user_initiated(item_->HasUserGesture());
707 request.set_file_basename(
708 item_->GetTargetFilePath().BaseName().AsUTF8Unsafe());
709 request.set_download_type(type_);
710 request.mutable_signature()->CopyFrom(signature_info_);
711 request.mutable_image_headers()->CopyFrom(image_headers_);
712 if (!request.SerializeToString(&client_download_request_data_)) {
713 FinishRequest(SAFE, REASON_INVALID_REQUEST_PROTO);
714 return;
715 }
716
717 VLOG(2) << "Sending a request for URL: "
718 << item_->GetUrlChain().back();
719 fetcher_.reset(net::URLFetcher::Create(0 /* ID used for testing */,
720 GetDownloadRequestUrl(),
721 net::URLFetcher::POST,
722 this));
723 fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
724 fetcher_->SetAutomaticallyRetryOn5xx(false); // Don't retry on error.
725 fetcher_->SetRequestContext(service_->request_context_getter_.get());
726 fetcher_->SetUploadData("application/octet-stream",
727 client_download_request_data_);
728 request_start_time_ = base::TimeTicks::Now();
729 UMA_HISTOGRAM_COUNTS("SBClientDownload.DownloadRequestPayloadSize",
730 client_download_request_data_.size());
731 fetcher_->Start();
732 }
733
PostFinishTask(DownloadCheckResult result,DownloadCheckResultReason reason)734 void PostFinishTask(DownloadCheckResult result,
735 DownloadCheckResultReason reason) {
736 BrowserThread::PostTask(
737 BrowserThread::UI,
738 FROM_HERE,
739 base::Bind(&CheckClientDownloadRequest::FinishRequest, this, result,
740 reason));
741 }
742
FinishRequest(DownloadCheckResult result,DownloadCheckResultReason reason)743 void FinishRequest(DownloadCheckResult result,
744 DownloadCheckResultReason reason) {
745 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
746 if (finished_) {
747 return;
748 }
749 finished_ = true;
750 // Ensure the timeout task is cancelled while we still have a non-zero
751 // refcount. (crbug.com/240449)
752 weakptr_factory_.InvalidateWeakPtrs();
753 if (!request_start_time_.is_null()) {
754 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.DownloadRequestNetworkStats",
755 reason,
756 REASON_MAX);
757 }
758 if (!timeout_start_time_.is_null()) {
759 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.DownloadRequestTimeoutStats",
760 reason,
761 REASON_MAX);
762 if (reason != REASON_REQUEST_CANCELED) {
763 UMA_HISTOGRAM_TIMES("SBClientDownload.DownloadRequestTimeoutDuration",
764 base::TimeTicks::Now() - timeout_start_time_);
765 }
766 }
767 if (service_) {
768 VLOG(2) << "SafeBrowsing download verdict for: "
769 << item_->DebugString(true) << " verdict:" << reason;
770 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats",
771 reason,
772 REASON_MAX);
773 callback_.Run(result);
774 item_->RemoveObserver(this);
775 item_ = NULL;
776 DownloadProtectionService* service = service_;
777 service_ = NULL;
778 service->RequestFinished(this);
779 // DownloadProtectionService::RequestFinished will decrement our refcount,
780 // so we may be deleted now.
781 } else {
782 callback_.Run(SAFE);
783 }
784 }
785
CertificateChainIsWhitelisted(const ClientDownloadRequest_CertificateChain & chain)786 bool CertificateChainIsWhitelisted(
787 const ClientDownloadRequest_CertificateChain& chain) {
788 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
789 if (chain.element_size() < 2) {
790 // We need to have both a signing certificate and its issuer certificate
791 // present to construct a whitelist entry.
792 return false;
793 }
794 scoped_refptr<net::X509Certificate> cert =
795 net::X509Certificate::CreateFromBytes(
796 chain.element(0).certificate().data(),
797 chain.element(0).certificate().size());
798 if (!cert.get()) {
799 return false;
800 }
801
802 for (int i = 1; i < chain.element_size(); ++i) {
803 scoped_refptr<net::X509Certificate> issuer =
804 net::X509Certificate::CreateFromBytes(
805 chain.element(i).certificate().data(),
806 chain.element(i).certificate().size());
807 if (!issuer.get()) {
808 return false;
809 }
810 std::vector<std::string> whitelist_strings;
811 DownloadProtectionService::GetCertificateWhitelistStrings(
812 *cert.get(), *issuer.get(), &whitelist_strings);
813 for (size_t j = 0; j < whitelist_strings.size(); ++j) {
814 if (database_manager_->MatchDownloadWhitelistString(
815 whitelist_strings[j])) {
816 VLOG(2) << "Certificate matched whitelist, cert="
817 << cert->subject().GetDisplayName()
818 << " issuer=" << issuer->subject().GetDisplayName();
819 return true;
820 }
821 }
822 cert = issuer;
823 }
824 return false;
825 }
826
827 // The DownloadItem we are checking. Will be NULL if the request has been
828 // canceled. Must be accessed only on UI thread.
829 content::DownloadItem* item_;
830 // Copies of data from |item_| for access on other threads.
831 std::vector<GURL> url_chain_;
832 GURL referrer_url_;
833 // URL chain of redirects leading to (but not including) |tab_url|.
834 std::vector<GURL> tab_redirects_;
835 // URL and referrer of the window the download was started from.
836 GURL tab_url_;
837 GURL tab_referrer_url_;
838
839 bool zipped_executable_;
840 ClientDownloadRequest_SignatureInfo signature_info_;
841 ClientDownloadRequest_ImageHeaders image_headers_;
842 CheckDownloadCallback callback_;
843 // Will be NULL if the request has been canceled.
844 DownloadProtectionService* service_;
845 scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor_;
846 scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
847 const bool pingback_enabled_;
848 scoped_ptr<net::URLFetcher> fetcher_;
849 scoped_refptr<SandboxedZipAnalyzer> analyzer_;
850 base::TimeTicks zip_analysis_start_time_;
851 bool finished_;
852 ClientDownloadRequest::DownloadType type_;
853 std::string client_download_request_data_;
854 CancelableRequestConsumer request_consumer_; // For HistoryService lookup.
855 base::WeakPtrFactory<CheckClientDownloadRequest> weakptr_factory_;
856 base::TimeTicks start_time_; // Used for stats.
857 base::TimeTicks timeout_start_time_;
858 base::TimeTicks request_start_time_;
859
860 DISALLOW_COPY_AND_ASSIGN(CheckClientDownloadRequest);
861 };
862
DownloadProtectionService(SafeBrowsingService * sb_service,net::URLRequestContextGetter * request_context_getter)863 DownloadProtectionService::DownloadProtectionService(
864 SafeBrowsingService* sb_service,
865 net::URLRequestContextGetter* request_context_getter)
866 : request_context_getter_(request_context_getter),
867 enabled_(false),
868 binary_feature_extractor_(new BinaryFeatureExtractor()),
869 download_request_timeout_ms_(kDownloadRequestTimeoutMs),
870 feedback_service_(new DownloadFeedbackService(
871 request_context_getter, BrowserThread::GetBlockingPool())) {
872
873 if (sb_service) {
874 ui_manager_ = sb_service->ui_manager();
875 database_manager_ = sb_service->database_manager();
876 }
877 }
878
~DownloadProtectionService()879 DownloadProtectionService::~DownloadProtectionService() {
880 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
881 CancelPendingRequests();
882 }
883
SetEnabled(bool enabled)884 void DownloadProtectionService::SetEnabled(bool enabled) {
885 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
886 if (enabled == enabled_) {
887 return;
888 }
889 enabled_ = enabled;
890 if (!enabled_) {
891 CancelPendingRequests();
892 }
893 }
894
CheckClientDownload(content::DownloadItem * item,const CheckDownloadCallback & callback)895 void DownloadProtectionService::CheckClientDownload(
896 content::DownloadItem* item,
897 const CheckDownloadCallback& callback) {
898 scoped_refptr<CheckClientDownloadRequest> request(
899 new CheckClientDownloadRequest(item, callback, this,
900 database_manager_,
901 binary_feature_extractor_.get()));
902 download_requests_.insert(request);
903 request->Start();
904 }
905
CheckDownloadUrl(const content::DownloadItem & item,const CheckDownloadCallback & callback)906 void DownloadProtectionService::CheckDownloadUrl(
907 const content::DownloadItem& item,
908 const CheckDownloadCallback& callback) {
909 DCHECK(!item.GetUrlChain().empty());
910 scoped_refptr<DownloadUrlSBClient> client(
911 new DownloadUrlSBClient(item, callback, ui_manager_, database_manager_));
912 // The client will release itself once it is done.
913 BrowserThread::PostTask(
914 BrowserThread::IO,
915 FROM_HERE,
916 base::Bind(&DownloadUrlSBClient::StartCheck, client));
917 }
918
IsSupportedDownload(const content::DownloadItem & item,const base::FilePath & target_path) const919 bool DownloadProtectionService::IsSupportedDownload(
920 const content::DownloadItem& item,
921 const base::FilePath& target_path) const {
922 // Currently, the UI only works on Windows. On Linux and Mac we still
923 // want to show the dangerous file type warning if the file is possibly
924 // dangerous which means we have to always return false here.
925 #if defined(OS_WIN)
926 DownloadCheckResultReason reason = REASON_MAX;
927 ClientDownloadRequest::DownloadType type =
928 ClientDownloadRequest::WIN_EXECUTABLE;
929 return (CheckClientDownloadRequest::IsSupportedDownload(item, target_path,
930 &reason, &type) &&
931 (ClientDownloadRequest::ANDROID_APK == type ||
932 ClientDownloadRequest::WIN_EXECUTABLE == type ||
933 ClientDownloadRequest::ZIPPED_EXECUTABLE == type));
934 #else
935 return false;
936 #endif
937 }
938
CancelPendingRequests()939 void DownloadProtectionService::CancelPendingRequests() {
940 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
941 for (std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
942 download_requests_.begin();
943 it != download_requests_.end();) {
944 // We need to advance the iterator before we cancel because canceling
945 // the request will invalidate it when RequestFinished is called below.
946 scoped_refptr<CheckClientDownloadRequest> tmp = *it++;
947 tmp->Cancel();
948 }
949 DCHECK(download_requests_.empty());
950 }
951
RequestFinished(CheckClientDownloadRequest * request)952 void DownloadProtectionService::RequestFinished(
953 CheckClientDownloadRequest* request) {
954 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
955 std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it =
956 download_requests_.find(request);
957 DCHECK(it != download_requests_.end());
958 download_requests_.erase(*it);
959 }
960
ShowDetailsForDownload(const content::DownloadItem & item,content::PageNavigator * navigator)961 void DownloadProtectionService::ShowDetailsForDownload(
962 const content::DownloadItem& item,
963 content::PageNavigator* navigator) {
964 GURL learn_more_url(chrome::kDownloadScanningLearnMoreURL);
965 navigator->OpenURL(
966 content::OpenURLParams(learn_more_url,
967 content::Referrer(),
968 NEW_FOREGROUND_TAB,
969 content::PAGE_TRANSITION_LINK,
970 false));
971 }
972
973 namespace {
974 // Escapes a certificate attribute so that it can be used in a whitelist
975 // entry. Currently, we only escape slashes, since they are used as a
976 // separator between attributes.
EscapeCertAttribute(const std::string & attribute)977 std::string EscapeCertAttribute(const std::string& attribute) {
978 std::string escaped;
979 for (size_t i = 0; i < attribute.size(); ++i) {
980 if (attribute[i] == '%') {
981 escaped.append("%25");
982 } else if (attribute[i] == '/') {
983 escaped.append("%2F");
984 } else {
985 escaped.push_back(attribute[i]);
986 }
987 }
988 return escaped;
989 }
990 } // namespace
991
992 // static
GetCertificateWhitelistStrings(const net::X509Certificate & certificate,const net::X509Certificate & issuer,std::vector<std::string> * whitelist_strings)993 void DownloadProtectionService::GetCertificateWhitelistStrings(
994 const net::X509Certificate& certificate,
995 const net::X509Certificate& issuer,
996 std::vector<std::string>* whitelist_strings) {
997 // The whitelist paths are in the format:
998 // cert/<ascii issuer fingerprint>[/CN=common_name][/O=org][/OU=unit]
999 //
1000 // Any of CN, O, or OU may be omitted from the whitelist entry, in which
1001 // case they match anything. However, the attributes that do appear will
1002 // always be in the order shown above. At least one attribute will always
1003 // be present.
1004
1005 const net::CertPrincipal& subject = certificate.subject();
1006 std::vector<std::string> ou_tokens;
1007 for (size_t i = 0; i < subject.organization_unit_names.size(); ++i) {
1008 ou_tokens.push_back(
1009 "/OU=" + EscapeCertAttribute(subject.organization_unit_names[i]));
1010 }
1011
1012 std::vector<std::string> o_tokens;
1013 for (size_t i = 0; i < subject.organization_names.size(); ++i) {
1014 o_tokens.push_back(
1015 "/O=" + EscapeCertAttribute(subject.organization_names[i]));
1016 }
1017
1018 std::string cn_token;
1019 if (!subject.common_name.empty()) {
1020 cn_token = "/CN=" + EscapeCertAttribute(subject.common_name);
1021 }
1022
1023 std::set<std::string> paths_to_check;
1024 if (!cn_token.empty()) {
1025 paths_to_check.insert(cn_token);
1026 }
1027 for (size_t i = 0; i < o_tokens.size(); ++i) {
1028 paths_to_check.insert(cn_token + o_tokens[i]);
1029 paths_to_check.insert(o_tokens[i]);
1030 for (size_t j = 0; j < ou_tokens.size(); ++j) {
1031 paths_to_check.insert(cn_token + o_tokens[i] + ou_tokens[j]);
1032 paths_to_check.insert(o_tokens[i] + ou_tokens[j]);
1033 }
1034 }
1035 for (size_t i = 0; i < ou_tokens.size(); ++i) {
1036 paths_to_check.insert(cn_token + ou_tokens[i]);
1037 paths_to_check.insert(ou_tokens[i]);
1038 }
1039
1040 std::string issuer_fp = base::HexEncode(issuer.fingerprint().data,
1041 sizeof(issuer.fingerprint().data));
1042 for (std::set<std::string>::iterator it = paths_to_check.begin();
1043 it != paths_to_check.end(); ++it) {
1044 whitelist_strings->push_back("cert/" + issuer_fp + *it);
1045 }
1046 }
1047
1048 // static
GetDownloadRequestUrl()1049 GURL DownloadProtectionService::GetDownloadRequestUrl() {
1050 GURL url(kDownloadRequestUrl);
1051 std::string api_key = google_apis::GetAPIKey();
1052 if (!api_key.empty())
1053 url = url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true));
1054
1055 return url;
1056 }
1057
1058 } // namespace safe_browsing
1059