300 lines
9.8 KiB
C++
300 lines
9.8 KiB
C++
// Copyright 2014 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <windows.h>
|
|
#include <wintrust.h>
|
|
|
|
#include "base/files/file_path.h"
|
|
#include "base/files/memory_mapped_file.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/path_service.h"
|
|
#include "base/win/pe_image_reader.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
using ::testing::_;
|
|
using ::testing::Gt;
|
|
using ::testing::NotNull;
|
|
using ::testing::Return;
|
|
using ::testing::StrictMock;
|
|
|
|
namespace base {
|
|
namespace win {
|
|
|
|
struct TestData {
|
|
const char* filename;
|
|
PeImageReader::WordSize word_size;
|
|
WORD machine_identifier;
|
|
WORD optional_header_size;
|
|
size_t number_of_sections;
|
|
size_t number_of_debug_entries;
|
|
};
|
|
|
|
// A test fixture parameterized on test data containing the name of a PE image
|
|
// to parse and the expected values to be read from it. The file is read from
|
|
// the src/base/test/data/pe_image_reader directory.
|
|
class PeImageReaderTest : public testing::TestWithParam<const TestData*> {
|
|
protected:
|
|
PeImageReaderTest() : expected_data_(GetParam()) {}
|
|
|
|
void SetUp() override {
|
|
ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_file_path_));
|
|
data_file_path_ = data_file_path_.AppendASCII("pe_image_reader");
|
|
data_file_path_ = data_file_path_.AppendASCII(expected_data_->filename);
|
|
|
|
ASSERT_TRUE(data_file_.Initialize(data_file_path_));
|
|
|
|
ASSERT_TRUE(
|
|
image_reader_.Initialize(data_file_.data(), data_file_.length()));
|
|
}
|
|
|
|
raw_ptr<const TestData> expected_data_;
|
|
FilePath data_file_path_;
|
|
MemoryMappedFile data_file_;
|
|
PeImageReader image_reader_;
|
|
};
|
|
|
|
TEST_P(PeImageReaderTest, GetWordSize) {
|
|
EXPECT_EQ(expected_data_->word_size, image_reader_.GetWordSize());
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetDosHeader) {
|
|
const IMAGE_DOS_HEADER* dos_header = image_reader_.GetDosHeader();
|
|
ASSERT_NE(nullptr, dos_header);
|
|
EXPECT_EQ(IMAGE_DOS_SIGNATURE, dos_header->e_magic);
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetCoffFileHeader) {
|
|
const IMAGE_FILE_HEADER* file_header = image_reader_.GetCoffFileHeader();
|
|
ASSERT_NE(nullptr, file_header);
|
|
EXPECT_EQ(expected_data_->machine_identifier, file_header->Machine);
|
|
EXPECT_EQ(expected_data_->optional_header_size,
|
|
file_header->SizeOfOptionalHeader);
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetOptionalHeaderData) {
|
|
size_t optional_header_size = 0;
|
|
const uint8_t* optional_header_data =
|
|
image_reader_.GetOptionalHeaderData(&optional_header_size);
|
|
ASSERT_NE(nullptr, optional_header_data);
|
|
EXPECT_EQ(expected_data_->optional_header_size, optional_header_size);
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetNumberOfSections) {
|
|
EXPECT_EQ(expected_data_->number_of_sections,
|
|
image_reader_.GetNumberOfSections());
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetSectionHeaderAt) {
|
|
size_t number_of_sections = image_reader_.GetNumberOfSections();
|
|
for (size_t i = 0; i < number_of_sections; ++i) {
|
|
const IMAGE_SECTION_HEADER* section_header =
|
|
image_reader_.GetSectionHeaderAt(i);
|
|
ASSERT_NE(nullptr, section_header);
|
|
}
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, InitializeFailTruncatedFile) {
|
|
// Compute the size of all headers through the section headers.
|
|
const IMAGE_SECTION_HEADER* last_section_header =
|
|
image_reader_.GetSectionHeaderAt(image_reader_.GetNumberOfSections() - 1);
|
|
const uint8_t* headers_end =
|
|
reinterpret_cast<const uint8_t*>(last_section_header) +
|
|
sizeof(*last_section_header);
|
|
size_t header_size = headers_end - data_file_.data();
|
|
PeImageReader short_reader;
|
|
|
|
// Initialize should succeed when all headers are present.
|
|
EXPECT_TRUE(short_reader.Initialize(data_file_.data(), header_size));
|
|
|
|
// But fail if anything is missing.
|
|
for (size_t i = 0; i < header_size; ++i) {
|
|
EXPECT_FALSE(short_reader.Initialize(data_file_.data(), i));
|
|
}
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetExportSection) {
|
|
size_t section_size = 0;
|
|
const uint8_t* export_section = image_reader_.GetExportSection(§ion_size);
|
|
ASSERT_NE(nullptr, export_section);
|
|
EXPECT_NE(0U, section_size);
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetNumberOfDebugEntries) {
|
|
EXPECT_EQ(expected_data_->number_of_debug_entries,
|
|
image_reader_.GetNumberOfDebugEntries());
|
|
}
|
|
|
|
TEST_P(PeImageReaderTest, GetDebugEntry) {
|
|
size_t number_of_debug_entries = image_reader_.GetNumberOfDebugEntries();
|
|
for (size_t i = 0; i < number_of_debug_entries; ++i) {
|
|
const uint8_t* raw_data = nullptr;
|
|
size_t raw_data_size = 0;
|
|
const IMAGE_DEBUG_DIRECTORY* entry =
|
|
image_reader_.GetDebugEntry(i, &raw_data, &raw_data_size);
|
|
EXPECT_NE(nullptr, entry);
|
|
EXPECT_NE(nullptr, raw_data);
|
|
EXPECT_NE(0U, raw_data_size);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
const TestData kTestData[] = {
|
|
{
|
|
"module_with_exports_x86.dll",
|
|
PeImageReader::WORD_SIZE_32,
|
|
IMAGE_FILE_MACHINE_I386,
|
|
sizeof(IMAGE_OPTIONAL_HEADER32),
|
|
4,
|
|
1,
|
|
},
|
|
{
|
|
"module_with_exports_x64.dll",
|
|
PeImageReader::WORD_SIZE_64,
|
|
IMAGE_FILE_MACHINE_AMD64,
|
|
sizeof(IMAGE_OPTIONAL_HEADER64),
|
|
5,
|
|
1,
|
|
},
|
|
};
|
|
|
|
} // namespace
|
|
|
|
INSTANTIATE_TEST_SUITE_P(WordSize32,
|
|
PeImageReaderTest,
|
|
testing::Values(&kTestData[0]));
|
|
INSTANTIATE_TEST_SUITE_P(WordSize64,
|
|
PeImageReaderTest,
|
|
testing::Values(&kTestData[1]));
|
|
|
|
// An object exposing a PeImageReader::EnumCertificatesCallback that invokes a
|
|
// virtual OnCertificate() method. This method is suitable for mocking in tests.
|
|
class CertificateReceiver {
|
|
public:
|
|
void* AsContext() { return this; }
|
|
static bool OnCertificateCallback(uint16_t revision,
|
|
uint16_t certificate_type,
|
|
const uint8_t* certificate_data,
|
|
size_t certificate_data_size,
|
|
void* context) {
|
|
return reinterpret_cast<CertificateReceiver*>(context)->OnCertificate(
|
|
revision, certificate_type, certificate_data, certificate_data_size);
|
|
}
|
|
|
|
protected:
|
|
CertificateReceiver() = default;
|
|
virtual ~CertificateReceiver() = default;
|
|
virtual bool OnCertificate(uint16_t revision,
|
|
uint16_t certificate_type,
|
|
const uint8_t* certificate_data,
|
|
size_t certificate_data_size) = 0;
|
|
};
|
|
|
|
class MockCertificateReceiver : public CertificateReceiver {
|
|
public:
|
|
MockCertificateReceiver() = default;
|
|
|
|
MockCertificateReceiver(const MockCertificateReceiver&) = delete;
|
|
MockCertificateReceiver& operator=(const MockCertificateReceiver&) = delete;
|
|
|
|
MOCK_METHOD4(OnCertificate, bool(uint16_t, uint16_t, const uint8_t*, size_t));
|
|
};
|
|
|
|
struct CertificateTestData {
|
|
const char* filename;
|
|
int num_signers;
|
|
};
|
|
|
|
// A test fixture parameterized on test data containing the name of a PE image
|
|
// to parse and the expected values to be read from it. The file is read from
|
|
// the src/chrome/test/data/safe_browsing/download_protection directory.
|
|
class PeImageReaderCertificateTest
|
|
: public testing::TestWithParam<const CertificateTestData*> {
|
|
protected:
|
|
PeImageReaderCertificateTest() : expected_data_(GetParam()) {}
|
|
|
|
void SetUp() override {
|
|
ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_file_path_));
|
|
data_file_path_ = data_file_path_.AppendASCII("pe_image_reader");
|
|
data_file_path_ = data_file_path_.AppendASCII(expected_data_->filename);
|
|
ASSERT_TRUE(data_file_.Initialize(data_file_path_));
|
|
ASSERT_TRUE(
|
|
image_reader_.Initialize(data_file_.data(), data_file_.length()));
|
|
}
|
|
|
|
raw_ptr<const CertificateTestData> expected_data_;
|
|
FilePath data_file_path_;
|
|
MemoryMappedFile data_file_;
|
|
PeImageReader image_reader_;
|
|
};
|
|
|
|
TEST_P(PeImageReaderCertificateTest, EnumCertificates) {
|
|
StrictMock<MockCertificateReceiver> receiver;
|
|
if (expected_data_->num_signers) {
|
|
EXPECT_CALL(receiver, OnCertificate(WIN_CERT_REVISION_2_0,
|
|
WIN_CERT_TYPE_PKCS_SIGNED_DATA,
|
|
NotNull(), Gt(0U)))
|
|
.Times(expected_data_->num_signers)
|
|
.WillRepeatedly(Return(true));
|
|
}
|
|
EXPECT_TRUE(image_reader_.EnumCertificates(
|
|
&CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
|
|
}
|
|
|
|
TEST_P(PeImageReaderCertificateTest, AbortEnum) {
|
|
StrictMock<MockCertificateReceiver> receiver;
|
|
if (expected_data_->num_signers) {
|
|
// Return false for the first cert, thereby stopping the enumeration.
|
|
EXPECT_CALL(receiver, OnCertificate(_, _, _, _)).WillOnce(Return(false));
|
|
EXPECT_FALSE(image_reader_.EnumCertificates(
|
|
&CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
|
|
} else {
|
|
// An unsigned file always reports true with no invocations of the callback.
|
|
EXPECT_TRUE(image_reader_.EnumCertificates(
|
|
&CertificateReceiver::OnCertificateCallback, receiver.AsContext()));
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
const CertificateTestData kCertificateTestData[] = {
|
|
{
|
|
"signed.exe",
|
|
1,
|
|
},
|
|
{
|
|
"unsigned.exe",
|
|
0,
|
|
},
|
|
{
|
|
"disable_outdated_build_detector.exe",
|
|
1,
|
|
},
|
|
{
|
|
"signed_twice.exe",
|
|
2,
|
|
},
|
|
};
|
|
|
|
} // namespace
|
|
|
|
INSTANTIATE_TEST_SUITE_P(SignedExe,
|
|
PeImageReaderCertificateTest,
|
|
testing::Values(&kCertificateTestData[0]));
|
|
INSTANTIATE_TEST_SUITE_P(UnsignedExe,
|
|
PeImageReaderCertificateTest,
|
|
testing::Values(&kCertificateTestData[1]));
|
|
INSTANTIATE_TEST_SUITE_P(DisableOutdatedBuildDetectorExe,
|
|
PeImageReaderCertificateTest,
|
|
testing::Values(&kCertificateTestData[2]));
|
|
INSTANTIATE_TEST_SUITE_P(SignedTwiceExe,
|
|
PeImageReaderCertificateTest,
|
|
testing::Values(&kCertificateTestData[3]));
|
|
|
|
} // namespace win
|
|
} // namespace base
|