Index: chrome/browser/safe_browsing/download_protection_service.cc |
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc |
index c62bc4a6c6e5041845f1c39643a78fc6f4992f20..cfd889c2ae10cb45d30ab4f6e6ae90665050fe06 100644 |
--- a/chrome/browser/safe_browsing/download_protection_service.cc |
+++ b/chrome/browser/safe_browsing/download_protection_service.cc |
@@ -22,6 +22,7 @@ |
#include "chrome/browser/ui/browser_list.h" |
#include "chrome/common/safe_browsing/csd.pb.h" |
#include "chrome/common/url_constants.h" |
+#include "chrome/common/zip_reader.h" |
#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/download_item.h" |
#include "content/public/browser/page_navigator.h" |
@@ -46,6 +47,10 @@ const char DownloadProtectionService::kDownloadRequestUrl[] = |
"https://sb-ssl.google.com/safebrowsing/clientreport/download"; |
namespace { |
+bool IsArchiveFile(const FilePath& file) { |
+ return file.MatchesExtension(FILE_PATH_LITERAL(".zip")); |
+} |
+ |
bool IsBinaryFile(const FilePath& file) { |
return ( |
// Executable extensions for MS Windows. |
@@ -64,7 +69,9 @@ bool IsBinaryFile(const FilePath& file) { |
file.MatchesExtension(FILE_PATH_LITERAL(".vbs")) || |
// Chrome extensions and android APKs are also reported. |
file.MatchesExtension(FILE_PATH_LITERAL(".crx")) || |
- file.MatchesExtension(FILE_PATH_LITERAL(".apk"))); |
+ file.MatchesExtension(FILE_PATH_LITERAL(".apk")) || |
+ // Archives _may_ contain binaries, we'll check in ExtractFileFeatures. |
+ IsArchiveFile(file)); |
} |
ClientDownloadRequest::DownloadType GetDownloadType(const FilePath& file) { |
@@ -73,6 +80,10 @@ ClientDownloadRequest::DownloadType GetDownloadType(const FilePath& file) { |
return ClientDownloadRequest::ANDROID_APK; |
else if (file.MatchesExtension(FILE_PATH_LITERAL(".crx"))) |
return ClientDownloadRequest::CHROME_EXTENSION; |
+ // For zip files, we use the ZIPPED_EXECUTABLE type since we will only send |
+ // the pingback if we find an executable inside the zip archive. |
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".zip"))) |
+ return ClientDownloadRequest::ZIPPED_EXECUTABLE; |
return ClientDownloadRequest::WIN_EXECUTABLE; |
} |
@@ -150,7 +161,7 @@ enum SBStatsType { |
} // namespace |
DownloadProtectionService::DownloadInfo::DownloadInfo() |
- : total_bytes(0), user_initiated(false) {} |
+ : total_bytes(0), user_initiated(false), zipped_executable(false) {} |
DownloadProtectionService::DownloadInfo::~DownloadInfo() {} |
@@ -163,9 +174,10 @@ std::string DownloadProtectionService::DownloadInfo::DebugString() const { |
} |
} |
return base::StringPrintf( |
- "DownloadInfo {addr:0x%p, download_url_chain:[%s], local_file:%s, " |
- "target_file:%s, referrer_url:%s, sha256_hash:%s, total_bytes:%" PRId64 |
- ", user_initiated: %s}", |
+ "DownloadInfo {addr:0x%p, download_url_chain:[%s], local_file:%" |
+ PRFilePath ", target_file:%" PRFilePath ", referrer_url:%s, " |
+ "sha256_hash:%s, total_bytes:%" PRId64 ", user_initiated: %s, " |
+ "zipped_executable: %s}", |
reinterpret_cast<const void*>(this), |
chain.c_str(), |
local_file.value().c_str(), |
@@ -173,7 +185,8 @@ std::string DownloadProtectionService::DownloadInfo::DebugString() const { |
referrer_url.spec().c_str(), |
base::HexEncode(sha256_hash.data(), sha256_hash.size()).c_str(), |
total_bytes, |
- user_initiated ? "true" : "false"); |
+ user_initiated ? "true" : "false", |
+ zipped_executable ? "true" : "false"); |
} |
// static |
@@ -488,6 +501,32 @@ class DownloadProtectionService::CheckClientDownloadRequest |
} |
void ExtractFileFeatures() { |
+ // If we're checking an archive file, look to see if there are any |
+ // executables inside. If not, we will skip the pingback for this |
+ // download. |
+ if (info_.target_file.MatchesExtension(FILE_PATH_LITERAL(".zip"))) { |
+ ExtractZipFeatures(); |
+ if (!info_.zipped_executable) { |
+ RecordImprovedProtectionStats(REASON_ARCHIVE_WITHOUT_BINARIES); |
+ PostFinishTask(SAFE); |
+ return; |
+ } |
+ } else { |
+ DCHECK(!IsArchiveFile(info_.target_file)); |
+ ExtractSignatureFeatures(); |
+ } |
+ |
+ // 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)); |
+ } |
+ |
+ void ExtractSignatureFeatures() { |
+ base::TimeTicks start_time = base::TimeTicks::Now(); |
signature_util_->CheckSignature(info_.local_file, &signature_info_); |
bool is_signed = (signature_info_.certificate_chain_size() > 0); |
if (is_signed) { |
@@ -496,14 +535,46 @@ class DownloadProtectionService::CheckClientDownloadRequest |
VLOG(2) << "Downloaded an unsigned binary: " << info_.local_file.value(); |
} |
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed); |
+ UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractSignatureFeaturesTime", |
+ base::TimeTicks::Now() - start_time); |
+ } |
- // 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)); |
+ void ExtractZipFeatures() { |
+ base::TimeTicks start_time = base::TimeTicks::Now(); |
+ zip::ZipReader reader; |
+ bool zip_file_has_archive = false; |
+ if (reader.Open(info_.local_file)) { |
+ for (; reader.HasMore(); reader.AdvanceToNextEntry()) { |
+ if (!reader.OpenCurrentEntryInZip()) { |
+ VLOG(1) << "Failed to open current entry in zip file: " |
+ << info_.local_file.value(); |
+ continue; |
+ } |
+ const FilePath& file = reader.current_entry_info()->file_path(); |
+ if (IsBinaryFile(file)) { |
+ // Don't consider an archived archive to be executable, but record |
+ // a histogram. |
+ if (IsArchiveFile(file)) { |
+ zip_file_has_archive = true; |
+ } else { |
+ VLOG(2) << "Downloaded a zipped executable: " |
+ << info_.local_file.value(); |
+ info_.zipped_executable = true; |
+ break; |
+ } |
+ } else { |
+ VLOG(3) << "Ignoring non-binary file: " << file.value(); |
+ } |
+ } |
+ } else { |
+ VLOG(1) << "Failed to open zip file: " << info_.local_file.value(); |
+ } |
+ UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasExecutable", |
+ info_.zipped_executable); |
+ UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasArchiveButNoExecutable", |
+ zip_file_has_archive && !info_.zipped_executable); |
+ UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractZipFeaturesTime", |
+ base::TimeTicks::Now() - start_time); |
} |
void CheckWhitelists() { |
@@ -763,7 +834,8 @@ bool DownloadProtectionService::IsSupportedDownload( |
return (CheckClientDownloadRequest::IsSupportedDownload(info, |
&reason, |
&type) && |
- ClientDownloadRequest::WIN_EXECUTABLE == type); |
+ (ClientDownloadRequest::WIN_EXECUTABLE == type || |
+ ClientDownloadRequest::ZIPPED_EXECUTABLE == type)); |
#else |
return false; |
#endif |