blob: 9a3f44223526e0a583b8bbc808c1866aa5608668 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/utility/safe_browsing/seven_zip_analyzer.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/common/safe_browsing/archive_analyzer_results.h"
#include "components/safe_browsing/content/common/file_type_policies.h"
#include "components/safe_browsing/core/common/features.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
// Must be after <windows.h>
#include <winbase.h>
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <sys/mman.h>
#endif
namespace safe_browsing {
SevenZipAnalyzer::SevenZipAnalyzer() = default;
SevenZipAnalyzer::~SevenZipAnalyzer() = default;
void SevenZipAnalyzer::OnOpenError(seven_zip::Result result) {
results()->success = false;
results()->analysis_result = ArchiveAnalysisResult::kFailedToOpen;
}
base::File SevenZipAnalyzer::OnTempFileRequest() {
return std::move(temp_file2_);
}
bool SevenZipAnalyzer::OnEntry(const seven_zip::EntryInfo& entry,
base::span<uint8_t>& output) {
if (entry.file_size == 0) {
// Empty files try to initialize the memory mapping with region {0, 0},
// which is confused with Region::kWholeFile. Since we can't truncate the
// file within the utility process sandbox, the file still has contents
// from a previous entry, and we end up mapping those contents. This leads
// to CHECK failures since `output.size()` does not match
// `entry.file_size`. Since the file is actually empty, we can skip the
// memory mapping here to avoid this.
output = base::span<uint8_t>();
return true;
}
mapped_file_.emplace();
bool mapped_file_ok = mapped_file_->Initialize(
temp_file_.Duplicate(), {0, static_cast<size_t>(entry.file_size)},
base::MemoryMappedFile::READ_WRITE_EXTEND);
if (!mapped_file_ok) {
results()->success = false;
results()->analysis_result = ArchiveAnalysisResult::kUnknown;
return false;
}
output = base::span<uint8_t>(mapped_file_->data(), mapped_file_->length());
return true;
}
bool SevenZipAnalyzer::OnDirectory(const seven_zip::EntryInfo& entry) {
return UpdateResultsForEntry(
temp_file_.Duplicate(), GetRootPath().Append(entry.file_path),
entry.file_size, entry.is_encrypted, /*is_directory=*/true,
/*contents_valid=*/!entry.is_encrypted);
}
bool SevenZipAnalyzer::EntryDone(seven_zip::Result result,
const seven_zip::EntryInfo& entry) {
// Since unpacking an encrypted entry is expected to fail, allow all results
// here for encrypted entries.
if (result == seven_zip::Result::kSuccess || entry.is_encrypted) {
// TODO(crbug/1373509): We have the entire file in memory, so it's silly
// to do all this work to flush it and read it back. Can we simplify this
// process? This also reduces the risk that the file is not flushed fully.
mapped_file_.reset();
if (!UpdateResultsForEntry(
temp_file_.Duplicate(), GetRootPath().Append(entry.file_path),
entry.file_size, entry.is_encrypted, /*is_directory=*/false,
/*contents_valid=*/!entry.is_encrypted)) {
awaiting_nested_ = true;
return false;
}
}
return true;
}
void SevenZipAnalyzer::Init() {
// Request two temp files.
GetTempFile(base::BindOnce(&SevenZipAnalyzer::OnGetTempFile,
weak_factory_.GetWeakPtr()));
GetTempFile(base::BindOnce(&SevenZipAnalyzer::OnGetTempFile,
weak_factory_.GetWeakPtr()));
}
bool SevenZipAnalyzer::ResumeExtraction() {
awaiting_nested_ = false;
reader_->Extract();
return !awaiting_nested_;
}
base::WeakPtr<ArchiveAnalyzer> SevenZipAnalyzer::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void SevenZipAnalyzer::OnGetTempFile(base::File temp_file) {
if (!temp_file.IsValid()) {
InitComplete(ArchiveAnalysisResult::kFailedToOpenTempFile);
return;
}
if (!temp_file_.IsValid()) {
temp_file_ = std::move(temp_file);
return;
} else {
temp_file2_ = std::move(temp_file);
}
// If the file is too big to unpack, return failure.
bool too_big_to_unpack =
base::checked_cast<uint64_t>(GetArchiveFile().GetLength()) >
FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("7z");
if (too_big_to_unpack) {
InitComplete(ArchiveAnalysisResult::kTooLarge);
return;
}
results()->success = true;
results()->analysis_result = ArchiveAnalysisResult::kValid;
reader_ =
seven_zip::SevenZipReader::Create(std::move(GetArchiveFile()), *this);
if (!reader_) {
// We will have been notified through OnOpenError and updated `results_`
// appropriately
InitComplete(results()->analysis_result);
return;
}
InitComplete(ArchiveAnalysisResult::kValid);
}
} // namespace safe_browsing