| // Copyright 2017 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 file contains the archive file analysis implementation for download |
| // protection, which runs in a sandboxed utility process. |
| |
| #include "chrome/common/safe_browsing/archive_analyzer_results.h" |
| |
| #include "base/files/file.h" |
| #include "base/i18n/streaming_utf8_validator.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/stl_util.h" |
| #include "build/build_config.h" |
| #include "chrome/common/safe_browsing/archive_analyzer_results.h" |
| #include "chrome/common/safe_browsing/binary_feature_extractor.h" |
| #include "chrome/common/safe_browsing/download_type_util.h" |
| #include "chrome/common/safe_browsing/file_type_policies.h" |
| #include "crypto/secure_hash.h" |
| #include "crypto/sha2.h" |
| |
| #if defined(OS_MACOSX) |
| #include <mach-o/fat.h> |
| #include <mach-o/loader.h> |
| #include "base/containers/span.h" |
| #include "chrome/common/safe_browsing/disk_image_type_sniffer_mac.h" |
| #include "chrome/common/safe_browsing/mach_o_image_reader_mac.h" |
| #endif // OS_MACOSX |
| |
| namespace safe_browsing { |
| |
| namespace { |
| |
| void SetLengthAndDigestForContainedFile( |
| const base::FilePath& path, |
| base::File* temp_file, |
| ClientDownloadRequest::ArchivedBinary* archived_binary) { |
| std::string file_basename(path.BaseName().AsUTF8Unsafe()); |
| if (base::StreamingUtf8Validator::Validate(file_basename)) |
| archived_binary->set_file_basename(file_basename); |
| archived_binary->set_length(temp_file->GetLength()); |
| |
| std::unique_ptr<crypto::SecureHash> hasher = |
| crypto::SecureHash::Create(crypto::SecureHash::SHA256); |
| |
| const size_t blockSize = 4096; |
| char block[4096]; |
| |
| temp_file->Seek(base::File::Whence::FROM_BEGIN, 0); |
| while (true) { |
| int bytesRead = temp_file->ReadAtCurrentPos(block, blockSize); |
| |
| if (bytesRead <= 0) |
| break; |
| |
| hasher->Update(block, bytesRead); |
| } |
| |
| uint8_t digest[crypto::kSHA256Length]; |
| hasher->Finish(digest, base::size(digest)); |
| archived_binary->mutable_digests()->set_sha256(digest, base::size(digest)); |
| } |
| |
| void AnalyzeContainedBinary( |
| const scoped_refptr<BinaryFeatureExtractor>& binary_feature_extractor, |
| base::File* temp_file, |
| ClientDownloadRequest::ArchivedBinary* archived_binary) { |
| if (!binary_feature_extractor->ExtractImageFeaturesFromFile( |
| temp_file->Duplicate(), BinaryFeatureExtractor::kDefaultOptions, |
| archived_binary->mutable_image_headers(), |
| archived_binary->mutable_signature()->mutable_signed_data())) { |
| archived_binary->clear_image_headers(); |
| archived_binary->clear_signature(); |
| } else if (!archived_binary->signature().signed_data_size()) { |
| // No SignedData blobs were extracted, so clear the |
| // signature field. |
| archived_binary->clear_signature(); |
| } |
| } |
| |
| #if defined(OS_MACOSX) |
| bool StringIsMachOMagic(std::string bytes) { |
| if (bytes.length() < sizeof(uint32_t)) |
| return false; |
| |
| uint32_t magic; |
| memcpy(&magic, bytes.c_str(), sizeof(uint32_t)); |
| |
| return MachOImageReader::IsMachOMagicValue(magic); |
| } |
| #endif // OS_MACOSX |
| |
| } // namespace |
| |
| ArchiveAnalyzerResults::ArchiveAnalyzerResults() |
| : success(false), has_executable(false), has_archive(false) {} |
| |
| ArchiveAnalyzerResults::ArchiveAnalyzerResults( |
| const ArchiveAnalyzerResults& other) = default; |
| |
| ArchiveAnalyzerResults::~ArchiveAnalyzerResults() {} |
| |
| void UpdateArchiveAnalyzerResultsWithFile(base::FilePath path, |
| base::File* file, |
| bool is_encrypted, |
| ArchiveAnalyzerResults* results) { |
| scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor( |
| new BinaryFeatureExtractor()); |
| bool current_entry_is_executable; |
| |
| #if defined(OS_MACOSX) |
| char magic[sizeof(uint32_t)]; |
| file->Read(0, magic, sizeof(uint32_t)); |
| |
| char dmg_header[DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()]; |
| file->Read(0, dmg_header, |
| DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()); |
| |
| current_entry_is_executable = |
| FileTypePolicies::GetInstance()->IsCheckedBinaryFile(path) || |
| StringIsMachOMagic(magic) || |
| DiskImageTypeSnifferMac::IsAppleDiskImageTrailer( |
| base::span<const uint8_t>( |
| reinterpret_cast<const uint8_t*>(dmg_header), |
| DiskImageTypeSnifferMac::AppleDiskImageTrailerSize())); |
| |
| // We can skip checking the trailer if we already know the file is executable. |
| if (!current_entry_is_executable) { |
| char trailer[DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()]; |
| file->Seek(base::File::Whence::FROM_END, |
| DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()); |
| file->ReadAtCurrentPos( |
| trailer, DiskImageTypeSnifferMac::AppleDiskImageTrailerSize()); |
| current_entry_is_executable = |
| DiskImageTypeSnifferMac::IsAppleDiskImageTrailer( |
| base::span<const uint8_t>( |
| reinterpret_cast<const uint8_t*>(trailer), |
| DiskImageTypeSnifferMac::AppleDiskImageTrailerSize())); |
| } |
| #else |
| current_entry_is_executable = |
| FileTypePolicies::GetInstance()->IsCheckedBinaryFile(path); |
| #endif // OS_MACOSX |
| |
| if (FileTypePolicies::GetInstance()->IsArchiveFile(path)) { |
| DVLOG(2) << "Downloaded a zipped archive: " << path.value(); |
| results->has_archive = true; |
| results->archived_archive_filenames.push_back(path.BaseName()); |
| ClientDownloadRequest::ArchivedBinary* archived_archive = |
| results->archived_binary.Add(); |
| archived_archive->set_download_type(ClientDownloadRequest::ARCHIVE); |
| archived_archive->set_is_encrypted(is_encrypted); |
| SetLengthAndDigestForContainedFile(path, file, archived_archive); |
| } else if (current_entry_is_executable) { |
| #if defined(OS_MACOSX) |
| // This check prevents running analysis on .app files since they are |
| // really just directories and will cause binary feature extraction |
| // to fail. |
| if (path.Extension().compare(".app") == 0) { |
| DVLOG(2) << "Downloaded a zipped .app directory: " << path.value(); |
| } else { |
| #endif // OS_MACOSX |
| DVLOG(2) << "Downloaded a zipped executable: " << path.value(); |
| results->has_executable = true; |
| ClientDownloadRequest::ArchivedBinary* archived_binary = |
| results->archived_binary.Add(); |
| archived_binary->set_is_encrypted(is_encrypted); |
| archived_binary->set_download_type( |
| download_type_util::GetDownloadType(path)); |
| SetLengthAndDigestForContainedFile(path, file, archived_binary); |
| AnalyzeContainedBinary(binary_feature_extractor, file, archived_binary); |
| #if defined(OS_MACOSX) |
| } |
| #endif // OS_MACOSX |
| } else { |
| DVLOG(3) << "Ignoring non-binary file: " << path.value(); |
| } |
| } |
| |
| } // namespace safe_browsing |