[Courgette] Add more checks in Win32 PE parsing to fix fuzzer failure.
Recently ClusterFuzz found a number of small PE files that trigger CHECK failures. This CL makes Courgette PE parsing code more robust, so more pathological cases can be identified and gracefully handled. Details: * Check that section headers fit within the image. * Check that section bodies lie within the image. * Check that section bodies virtual addresses are bounded by |size_of_image_|. * Add DisassemblerWin32::IsRvaRangeInBounds() to do this check.. * Check that PE header does not appear too early and cause pathological overlap. * For DisassemblerWin32: In ParseHeader() and QuickDetect(): * Check that ranges of all data read are within image. Bug: 934112,934141,936741 Change-Id: Iaf9be4c5fbd0efb31b47d61d86b7001f7674dde7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1549724 Reviewed-by: Samuel Huang <huangs@chromium.org> Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> Commit-Queue: Samuel Huang <huangs@chromium.org> Cr-Commit-Position: refs/heads/master@{#647297}
This commit is contained in:
@@ -67,7 +67,7 @@ FileOffset DisassemblerWin32::RVAToFileOffset(RVA rva) const {
|
|||||||
// structure.
|
// structure.
|
||||||
//
|
//
|
||||||
bool DisassemblerWin32::ParseHeader() {
|
bool DisassemblerWin32::ParseHeader() {
|
||||||
if (length() < kOffsetOfFileAddressOfNewExeHeader + 4 /*size*/)
|
if (!IsRangeInBounds(kOffsetOfFileAddressOfNewExeHeader, 4))
|
||||||
return Bad("Too small");
|
return Bad("Too small");
|
||||||
|
|
||||||
// Have 'MZ' magic for a DOS header?
|
// Have 'MZ' magic for a DOS header?
|
||||||
@@ -75,100 +75,98 @@ bool DisassemblerWin32::ParseHeader() {
|
|||||||
return Bad("Not MZ");
|
return Bad("Not MZ");
|
||||||
|
|
||||||
// offset from DOS header to PE header is stored in DOS header.
|
// offset from DOS header to PE header is stored in DOS header.
|
||||||
FileOffset file_offset = static_cast<FileOffset>(
|
FileOffset pe_header_offset = static_cast<FileOffset>(
|
||||||
ReadU32(start(), kOffsetOfFileAddressOfNewExeHeader));
|
ReadU32(start(), kOffsetOfFileAddressOfNewExeHeader));
|
||||||
|
if (pe_header_offset % 8 != 0)
|
||||||
if (file_offset >= length())
|
|
||||||
return Bad("Bad offset to PE header");
|
|
||||||
|
|
||||||
const uint8_t* const pe_header = FileOffsetToPointer(file_offset);
|
|
||||||
const size_t kMinPEHeaderSize = 4 /*signature*/ + kSizeOfCoffHeader;
|
|
||||||
if (pe_header <= start() || pe_header >= end() - kMinPEHeaderSize)
|
|
||||||
return Bad("Bad file offset to PE header");
|
|
||||||
|
|
||||||
if (file_offset % 8 != 0)
|
|
||||||
return Bad("Misaligned PE header");
|
return Bad("Misaligned PE header");
|
||||||
|
if (pe_header_offset < kOffsetOfFileAddressOfNewExeHeader + 4)
|
||||||
|
return Bad("PE header pathological overlap");
|
||||||
|
if (!IsRangeInBounds(pe_header_offset, kMinPeHeaderSize))
|
||||||
|
return Bad("PE header past end of file");
|
||||||
|
|
||||||
|
const uint8_t* const pe_header = FileOffsetToPointer(pe_header_offset);
|
||||||
|
|
||||||
// The 'PE' header is an IMAGE_NT_HEADERS structure as defined in WINNT.H.
|
// The 'PE' header is an IMAGE_NT_HEADERS structure as defined in WINNT.H.
|
||||||
// See http://msdn.microsoft.com/en-us/library/ms680336(VS.85).aspx
|
// See http://msdn.microsoft.com/en-us/library/ms680336(VS.85).aspx
|
||||||
//
|
//
|
||||||
// The first field of the IMAGE_NT_HEADERS is the signature.
|
// The first field of the IMAGE_NT_HEADERS is the signature.
|
||||||
if (!(pe_header[0] == 'P' && pe_header[1] == 'E' && pe_header[2] == 0 &&
|
if (!(pe_header[0] == 'P' && pe_header[1] == 'E' && pe_header[2] == 0 &&
|
||||||
pe_header[3] == 0))
|
pe_header[3] == 0)) {
|
||||||
return Bad("No PE signature");
|
return Bad("No PE signature");
|
||||||
|
}
|
||||||
|
|
||||||
// The second field of the IMAGE_NT_HEADERS is the COFF header.
|
// The second field of the IMAGE_NT_HEADERS is the COFF header.
|
||||||
// The COFF header is also called an IMAGE_FILE_HEADER
|
// The COFF header is also called an IMAGE_FILE_HEADER
|
||||||
// http://msdn.microsoft.com/en-us/library/ms680313(VS.85).aspx
|
// http://msdn.microsoft.com/en-us/library/ms680313(VS.85).aspx
|
||||||
const uint8_t* const coff_header = pe_header + 4;
|
FileOffset coff_header_offset = pe_header_offset + 4;
|
||||||
|
if (!IsRangeInBounds(coff_header_offset, kSizeOfCoffHeader))
|
||||||
|
return Bad("COFF header past end of file");
|
||||||
|
const uint8_t* const coff_header = start() + coff_header_offset;
|
||||||
machine_type_ = ReadU16(coff_header, 0);
|
machine_type_ = ReadU16(coff_header, 0);
|
||||||
number_of_sections_ = ReadU16(coff_header, 2);
|
number_of_sections_ = ReadU16(coff_header, 2);
|
||||||
size_of_optional_header_ = ReadU16(coff_header, 16);
|
size_of_optional_header_ = ReadU16(coff_header, 16);
|
||||||
|
|
||||||
// The rest of the IMAGE_NT_HEADERS is the IMAGE_OPTIONAL_HEADER(32|64)
|
|
||||||
const uint8_t* const optional_header = coff_header + kSizeOfCoffHeader;
|
|
||||||
optional_header_ = optional_header;
|
|
||||||
|
|
||||||
if (optional_header + size_of_optional_header_ >= end())
|
|
||||||
return Bad("Optional header past end of file");
|
|
||||||
|
|
||||||
// Check we can read the magic.
|
// Check we can read the magic.
|
||||||
if (size_of_optional_header_ < 2)
|
if (size_of_optional_header_ < 2)
|
||||||
return Bad("Optional header no magic");
|
return Bad("Optional header no magic");
|
||||||
|
// Check that we can read the rest of the the fixed fields. Data directories
|
||||||
|
// directly follow the fixed fields of the IMAGE_OPTIONAL_HEADER.
|
||||||
|
if (size_of_optional_header_ < RelativeOffsetOfDataDirectories())
|
||||||
|
return Bad("Optional header too short");
|
||||||
|
|
||||||
uint16_t magic = ReadU16(optional_header, 0);
|
// The rest of the IMAGE_NT_HEADERS is the IMAGE_OPTIONAL_HEADER(32|64)
|
||||||
|
FileOffset optional_header_offset = pe_header_offset + kMinPeHeaderSize;
|
||||||
|
if (!IsRangeInBounds(optional_header_offset, size_of_optional_header_))
|
||||||
|
return Bad("Optional header past end of file");
|
||||||
|
optional_header_ = start() + optional_header_offset;
|
||||||
|
|
||||||
|
uint16_t magic = ReadU16(optional_header_, 0);
|
||||||
switch (kind()) {
|
switch (kind()) {
|
||||||
case EXE_WIN_32_X86:
|
case EXE_WIN_32_X86:
|
||||||
if (magic != kImageNtOptionalHdr32Magic) {
|
if (magic != kImageNtOptionalHdr32Magic)
|
||||||
return Bad("64 bit executables are not supported by this disassembler");
|
return Bad("64 bit executables are not supported by this disassembler");
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXE_WIN_32_X64:
|
case EXE_WIN_32_X64:
|
||||||
if (magic != kImageNtOptionalHdr64Magic) {
|
if (magic != kImageNtOptionalHdr64Magic)
|
||||||
return Bad("32 bit executables are not supported by this disassembler");
|
return Bad("32 bit executables are not supported by this disassembler");
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Bad("Unrecognized magic");
|
return Bad("Unrecognized magic");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that we can read the rest of the the fixed fields. Data directories
|
|
||||||
// directly follow the fixed fields of the IMAGE_OPTIONAL_HEADER.
|
|
||||||
if (size_of_optional_header_ < OffsetOfDataDirectories())
|
|
||||||
return Bad("Optional header too short");
|
|
||||||
|
|
||||||
// The optional header is either an IMAGE_OPTIONAL_HEADER32 or
|
// The optional header is either an IMAGE_OPTIONAL_HEADER32 or
|
||||||
// IMAGE_OPTIONAL_HEADER64
|
// IMAGE_OPTIONAL_HEADER64
|
||||||
// http://msdn.microsoft.com/en-us/library/ms680339(VS.85).aspx
|
// http://msdn.microsoft.com/en-us/library/ms680339(VS.85).aspx
|
||||||
//
|
//
|
||||||
// Copy the fields we care about.
|
// Copy the fields we care about.
|
||||||
size_of_code_ = ReadU32(optional_header, 4);
|
size_of_code_ = ReadU32(optional_header_, 4);
|
||||||
size_of_initialized_data_ = ReadU32(optional_header, 8);
|
size_of_initialized_data_ = ReadU32(optional_header_, 8);
|
||||||
size_of_uninitialized_data_ = ReadU32(optional_header, 12);
|
size_of_uninitialized_data_ = ReadU32(optional_header_, 12);
|
||||||
base_of_code_ = ReadU32(optional_header, 20);
|
base_of_code_ = ReadU32(optional_header_, 20);
|
||||||
|
|
||||||
switch (kind()) {
|
switch (kind()) {
|
||||||
case EXE_WIN_32_X86:
|
case EXE_WIN_32_X86:
|
||||||
base_of_data_ = ReadU32(optional_header, 24);
|
base_of_data_ = ReadU32(optional_header_, 24);
|
||||||
image_base_ = ReadU32(optional_header, 28);
|
image_base_ = ReadU32(optional_header_, 28);
|
||||||
size_of_image_ = ReadU32(optional_header, 56);
|
size_of_image_ = ReadU32(optional_header_, 56);
|
||||||
number_of_data_directories_ = ReadU32(optional_header, 92);
|
number_of_data_directories_ = ReadU32(optional_header_, 92);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXE_WIN_32_X64:
|
case EXE_WIN_32_X64:
|
||||||
base_of_data_ = 0;
|
base_of_data_ = 0;
|
||||||
image_base_ = ReadU64(optional_header, 24);
|
image_base_ = ReadU64(optional_header_, 24);
|
||||||
size_of_image_ = ReadU32(optional_header, 56);
|
size_of_image_ = ReadU32(optional_header_, 56);
|
||||||
number_of_data_directories_ = ReadU32(optional_header, 108);
|
number_of_data_directories_ = ReadU32(optional_header_, 108);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
NOTREACHED();
|
NOTREACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size_of_image_ >= 0x80000000U)
|
||||||
|
return Bad("Invalid SizeOfImage");
|
||||||
|
|
||||||
if (size_of_code_ >= length() || size_of_initialized_data_ >= length() ||
|
if (size_of_code_ >= length() || size_of_initialized_data_ >= length() ||
|
||||||
size_of_code_ + size_of_initialized_data_ >= length()) {
|
size_of_code_ + size_of_initialized_data_ >= length()) {
|
||||||
// This validation fires on some perfectly fine executables.
|
// This validation fires on some perfectly fine executables.
|
||||||
@@ -188,15 +186,19 @@ bool DisassemblerWin32::ParseHeader() {
|
|||||||
b &= ReadDataDirectory(12, &import_address_table_);
|
b &= ReadDataDirectory(12, &import_address_table_);
|
||||||
b &= ReadDataDirectory(13, &delay_import_descriptor_);
|
b &= ReadDataDirectory(13, &delay_import_descriptor_);
|
||||||
b &= ReadDataDirectory(14, &clr_runtime_header_);
|
b &= ReadDataDirectory(14, &clr_runtime_header_);
|
||||||
if (!b) {
|
if (!b)
|
||||||
return Bad("Malformed data directory");
|
return Bad("Malformed data directory");
|
||||||
}
|
|
||||||
|
|
||||||
// Sections follow the optional header.
|
// Sections follow the optional header.
|
||||||
sections_ = reinterpret_cast<const Section*>(optional_header +
|
FileOffset sections_offset =
|
||||||
size_of_optional_header_);
|
optional_header_offset + size_of_optional_header_;
|
||||||
size_t detected_length = 0;
|
if (!IsArrayInBounds(sections_offset, number_of_sections_, sizeof(Section)))
|
||||||
|
return Bad("Sections past end of file");
|
||||||
|
sections_ = reinterpret_cast<const Section*>(start() + sections_offset);
|
||||||
|
if (!CheckSectionRanges())
|
||||||
|
return Bad("Out of bound section");
|
||||||
|
|
||||||
|
size_t detected_length = 0;
|
||||||
for (int i = 0; i < number_of_sections_; ++i) {
|
for (int i = 0; i < number_of_sections_; ++i) {
|
||||||
const Section* section = §ions_[i];
|
const Section* section = §ions_[i];
|
||||||
|
|
||||||
@@ -318,29 +320,47 @@ std::string DisassemblerWin32::SectionName(const Section* section) {
|
|||||||
bool DisassemblerWin32::QuickDetect(const uint8_t* start,
|
bool DisassemblerWin32::QuickDetect(const uint8_t* start,
|
||||||
size_t length,
|
size_t length,
|
||||||
uint16_t magic) {
|
uint16_t magic) {
|
||||||
if (length < kOffsetOfFileAddressOfNewExeHeader + 4 /* size */)
|
if (length < kOffsetOfFileAddressOfNewExeHeader + 4)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Have 'MZ' magic for a DOS header?
|
// Have 'MZ' magic for a DOS header?
|
||||||
if (start[0] != 'M' || start[1] != 'Z')
|
if (start[0] != 'M' || start[1] != 'Z')
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
FileOffset file_offset = static_cast<FileOffset>(
|
FileOffset pe_header_offset = static_cast<FileOffset>(
|
||||||
ReadU32(start, kOffsetOfFileAddressOfNewExeHeader));
|
ReadU32(start, kOffsetOfFileAddressOfNewExeHeader));
|
||||||
if (file_offset >= length || file_offset % 8 != 0)
|
if (pe_header_offset % 8 != 0 ||
|
||||||
|
pe_header_offset < kOffsetOfFileAddressOfNewExeHeader + 4 ||
|
||||||
|
pe_header_offset >= length ||
|
||||||
|
length - pe_header_offset < kMinPeHeaderSize) {
|
||||||
return false;
|
return false;
|
||||||
const uint8_t* const pe_header = start + file_offset;
|
}
|
||||||
const size_t kMinPEHeaderSize = 4 /*signature*/ + kSizeOfCoffHeader;
|
const uint8_t* pe_header = start + pe_header_offset;
|
||||||
if (pe_header <= start || pe_header + kMinPEHeaderSize >= start + length)
|
if (!(pe_header[0] == 'P' && pe_header[1] == 'E' && pe_header[2] == 0 &&
|
||||||
|
pe_header[3] == 0)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const uint8_t* optional_header = pe_header + 4 + kSizeOfCoffHeader;
|
FileOffset optional_header_offset = pe_header_offset + kMinPeHeaderSize;
|
||||||
// Check we can read the magic.
|
if (optional_header_offset >= length || length - optional_header_offset < 2)
|
||||||
if (optional_header + 2 >= start + length)
|
|
||||||
return false;
|
|
||||||
if (magic != ReadU16(optional_header, 0))
|
|
||||||
return false;
|
return false;
|
||||||
|
const uint8_t* optional_header = start + optional_header_offset;
|
||||||
|
return magic == ReadU16(optional_header, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DisassemblerWin32::IsRvaRangeInBounds(size_t start, size_t length) {
|
||||||
|
return start < size_of_image_ && length <= size_of_image_ - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DisassemblerWin32::CheckSectionRanges() {
|
||||||
|
for (int i = 0; i < number_of_sections_; ++i) {
|
||||||
|
const Section* section = §ions_[i];
|
||||||
|
if (!IsRangeInBounds(section->file_offset_of_raw_data,
|
||||||
|
section->size_of_raw_data) ||
|
||||||
|
!IsRvaRangeInBounds(section->virtual_address, section->virtual_size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +667,7 @@ const Section* DisassemblerWin32::FindNextSection(
|
|||||||
bool DisassemblerWin32::ReadDataDirectory(int index,
|
bool DisassemblerWin32::ReadDataDirectory(int index,
|
||||||
ImageDataDirectory* directory) {
|
ImageDataDirectory* directory) {
|
||||||
if (index < number_of_data_directories_) {
|
if (index < number_of_data_directories_) {
|
||||||
FileOffset file_offset = index * 8 + OffsetOfDataDirectories();
|
FileOffset file_offset = index * 8 + RelativeOffsetOfDataDirectories();
|
||||||
if (file_offset >= size_of_optional_header_)
|
if (file_offset >= size_of_optional_header_)
|
||||||
return Bad("Number of data directories inconsistent");
|
return Bad("Number of data directories inconsistent");
|
||||||
const uint8_t* data_directory = optional_header_ + file_offset;
|
const uint8_t* data_directory = optional_header_ + file_offset;
|
||||||
|
@@ -55,8 +55,11 @@ class DisassemblerWin32 : public Disassembler {
|
|||||||
// which will be checked against the detected one.
|
// which will be checked against the detected one.
|
||||||
static bool QuickDetect(const uint8_t* start, size_t length, uint16_t magic);
|
static bool QuickDetect(const uint8_t* start, size_t length, uint16_t magic);
|
||||||
|
|
||||||
bool ParseAbs32Relocs();
|
// Returns true if the given RVA range lies within [0, |size_of_image_|).
|
||||||
void ParseRel32RelocsFromSections();
|
bool IsRvaRangeInBounds(size_t start, size_t length);
|
||||||
|
|
||||||
|
// Returns whether all sections lie within image.
|
||||||
|
bool CheckSectionRanges();
|
||||||
|
|
||||||
// Disassembler interfaces.
|
// Disassembler interfaces.
|
||||||
bool ExtractAbs32Locations() override;
|
bool ExtractAbs32Locations() override;
|
||||||
@@ -91,7 +94,7 @@ class DisassemblerWin32 : public Disassembler {
|
|||||||
InstructionReceptor* receptor) const = 0;
|
InstructionReceptor* receptor) const = 0;
|
||||||
// Returns true if type is recognized.
|
// Returns true if type is recognized.
|
||||||
virtual bool SupportsRelTableType(int type) const = 0;
|
virtual bool SupportsRelTableType(int type) const = 0;
|
||||||
virtual uint16_t OffsetOfDataDirectories() const = 0;
|
virtual uint16_t RelativeOffsetOfDataDirectories() const = 0;
|
||||||
|
|
||||||
#if COURGETTE_HISTOGRAM_TARGETS
|
#if COURGETTE_HISTOGRAM_TARGETS
|
||||||
void HistogramTargets(const char* kind, const std::map<RVA, int>& map) const;
|
void HistogramTargets(const char* kind, const std::map<RVA, int>& map) const;
|
||||||
@@ -132,6 +135,7 @@ class DisassemblerWin32 : public Disassembler {
|
|||||||
RVA base_of_data_ = 0;
|
RVA base_of_data_ = 0;
|
||||||
|
|
||||||
uint64_t image_base_ = 0; // Range limited to 32 bits for 32 bit executable.
|
uint64_t image_base_ = 0; // Range limited to 32 bits for 32 bit executable.
|
||||||
|
// Specifies size of loaded PE in memory, and provides bound on RVA.
|
||||||
uint32_t size_of_image_ = 0;
|
uint32_t size_of_image_ = 0;
|
||||||
int number_of_data_directories_ = 0;
|
int number_of_data_directories_ = 0;
|
||||||
|
|
||||||
|
@@ -44,7 +44,7 @@ class DisassemblerWin32X64 : public DisassemblerWin32 {
|
|||||||
bool SupportsRelTableType(int type) const override {
|
bool SupportsRelTableType(int type) const override {
|
||||||
return type == 10; // IMAGE_REL_BASED_DIR64
|
return type == 10; // IMAGE_REL_BASED_DIR64
|
||||||
}
|
}
|
||||||
uint16_t OffsetOfDataDirectories() const override {
|
uint16_t RelativeOffsetOfDataDirectories() const override {
|
||||||
return kOffsetOfDataDirectoryFromImageOptionalHeader64;
|
return kOffsetOfDataDirectoryFromImageOptionalHeader64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,7 +44,7 @@ class DisassemblerWin32X86 : public DisassemblerWin32 {
|
|||||||
bool SupportsRelTableType(int type) const override {
|
bool SupportsRelTableType(int type) const override {
|
||||||
return type == 3; // IMAGE_REL_BASED_HIGHLOW
|
return type == 3; // IMAGE_REL_BASED_HIGHLOW
|
||||||
}
|
}
|
||||||
uint16_t OffsetOfDataDirectories() const override {
|
uint16_t RelativeOffsetOfDataDirectories() const override {
|
||||||
return kOffsetOfDataDirectoryFromImageOptionalHeader32;
|
return kOffsetOfDataDirectoryFromImageOptionalHeader32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
#ifndef TYPES_WIN_PE_H_
|
#ifndef COURGETTE_TYPES_WIN_PE_H_
|
||||||
#define TYPES_WIN_PE_H_
|
#define COURGETTE_TYPES_WIN_PE_H_
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -58,8 +58,10 @@ const uint16_t kImageNtOptionalHdr32Magic = 0x10b;
|
|||||||
const uint16_t kImageNtOptionalHdr64Magic = 0x20b;
|
const uint16_t kImageNtOptionalHdr64Magic = 0x20b;
|
||||||
|
|
||||||
const size_t kSizeOfCoffHeader = 20;
|
const size_t kSizeOfCoffHeader = 20;
|
||||||
|
const size_t kMinPeHeaderSize = 4 /*signature*/ + kSizeOfCoffHeader;
|
||||||
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader32 = 96;
|
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader32 = 96;
|
||||||
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader64 = 112;
|
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader64 = 112;
|
||||||
|
|
||||||
} // namespace
|
} // namespace courgette
|
||||||
#endif // TYPES_WIN_PE_H_
|
|
||||||
|
#endif // COURGETTE_TYPES_WIN_PE_H_
|
||||||
|
Reference in New Issue
Block a user