0

[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:
Samuel Huang
2019-04-03 15:53:13 +00:00
committed by Commit Bot
parent ff83e6c2b6
commit a34dce5bf8
5 changed files with 95 additions and 69 deletions

@ -67,7 +67,7 @@ FileOffset DisassemblerWin32::RVAToFileOffset(RVA rva) const {
// structure.
//
bool DisassemblerWin32::ParseHeader() {
if (length() < kOffsetOfFileAddressOfNewExeHeader + 4 /*size*/)
if (!IsRangeInBounds(kOffsetOfFileAddressOfNewExeHeader, 4))
return Bad("Too small");
// Have 'MZ' magic for a DOS header?
@ -75,100 +75,98 @@ bool DisassemblerWin32::ParseHeader() {
return Bad("Not MZ");
// 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));
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)
if (pe_header_offset % 8 != 0)
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.
// See http://msdn.microsoft.com/en-us/library/ms680336(VS.85).aspx
//
// The first field of the IMAGE_NT_HEADERS is the signature.
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");
}
// The second field of the IMAGE_NT_HEADERS is the COFF header.
// The COFF header is also called an IMAGE_FILE_HEADER
// 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);
number_of_sections_ = ReadU16(coff_header, 2);
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.
if (size_of_optional_header_ < 2)
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()) {
case EXE_WIN_32_X86:
if (magic != kImageNtOptionalHdr32Magic) {
if (magic != kImageNtOptionalHdr32Magic)
return Bad("64 bit executables are not supported by this disassembler");
}
break;
case EXE_WIN_32_X64:
if (magic != kImageNtOptionalHdr64Magic) {
if (magic != kImageNtOptionalHdr64Magic)
return Bad("32 bit executables are not supported by this disassembler");
}
break;
default:
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
// IMAGE_OPTIONAL_HEADER64
// http://msdn.microsoft.com/en-us/library/ms680339(VS.85).aspx
//
// Copy the fields we care about.
size_of_code_ = ReadU32(optional_header, 4);
size_of_initialized_data_ = ReadU32(optional_header, 8);
size_of_uninitialized_data_ = ReadU32(optional_header, 12);
base_of_code_ = ReadU32(optional_header, 20);
size_of_code_ = ReadU32(optional_header_, 4);
size_of_initialized_data_ = ReadU32(optional_header_, 8);
size_of_uninitialized_data_ = ReadU32(optional_header_, 12);
base_of_code_ = ReadU32(optional_header_, 20);
switch (kind()) {
case EXE_WIN_32_X86:
base_of_data_ = ReadU32(optional_header, 24);
image_base_ = ReadU32(optional_header, 28);
size_of_image_ = ReadU32(optional_header, 56);
number_of_data_directories_ = ReadU32(optional_header, 92);
base_of_data_ = ReadU32(optional_header_, 24);
image_base_ = ReadU32(optional_header_, 28);
size_of_image_ = ReadU32(optional_header_, 56);
number_of_data_directories_ = ReadU32(optional_header_, 92);
break;
case EXE_WIN_32_X64:
base_of_data_ = 0;
image_base_ = ReadU64(optional_header, 24);
size_of_image_ = ReadU32(optional_header, 56);
number_of_data_directories_ = ReadU32(optional_header, 108);
image_base_ = ReadU64(optional_header_, 24);
size_of_image_ = ReadU32(optional_header_, 56);
number_of_data_directories_ = ReadU32(optional_header_, 108);
break;
default:
NOTREACHED();
}
if (size_of_image_ >= 0x80000000U)
return Bad("Invalid SizeOfImage");
if (size_of_code_ >= length() || size_of_initialized_data_ >= length() ||
size_of_code_ + size_of_initialized_data_ >= length()) {
// This validation fires on some perfectly fine executables.
@ -188,15 +186,19 @@ bool DisassemblerWin32::ParseHeader() {
b &= ReadDataDirectory(12, &import_address_table_);
b &= ReadDataDirectory(13, &delay_import_descriptor_);
b &= ReadDataDirectory(14, &clr_runtime_header_);
if (!b) {
if (!b)
return Bad("Malformed data directory");
}
// Sections follow the optional header.
sections_ = reinterpret_cast<const Section*>(optional_header +
size_of_optional_header_);
size_t detected_length = 0;
FileOffset sections_offset =
optional_header_offset + size_of_optional_header_;
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) {
const Section* section = &sections_[i];
@ -318,29 +320,47 @@ std::string DisassemblerWin32::SectionName(const Section* section) {
bool DisassemblerWin32::QuickDetect(const uint8_t* start,
size_t length,
uint16_t magic) {
if (length < kOffsetOfFileAddressOfNewExeHeader + 4 /* size */)
if (length < kOffsetOfFileAddressOfNewExeHeader + 4)
return false;
// Have 'MZ' magic for a DOS header?
if (start[0] != 'M' || start[1] != 'Z')
return false;
FileOffset file_offset = static_cast<FileOffset>(
FileOffset pe_header_offset = static_cast<FileOffset>(
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;
const uint8_t* const pe_header = start + file_offset;
const size_t kMinPEHeaderSize = 4 /*signature*/ + kSizeOfCoffHeader;
if (pe_header <= start || pe_header + kMinPEHeaderSize >= start + length)
}
const uint8_t* pe_header = start + pe_header_offset;
if (!(pe_header[0] == 'P' && pe_header[1] == 'E' && pe_header[2] == 0 &&
pe_header[3] == 0)) {
return false;
}
const uint8_t* optional_header = pe_header + 4 + kSizeOfCoffHeader;
// Check we can read the magic.
if (optional_header + 2 >= start + length)
return false;
if (magic != ReadU16(optional_header, 0))
FileOffset optional_header_offset = pe_header_offset + kMinPeHeaderSize;
if (optional_header_offset >= length || length - optional_header_offset < 2)
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 = &sections_[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;
}
@ -647,7 +667,7 @@ const Section* DisassemblerWin32::FindNextSection(
bool DisassemblerWin32::ReadDataDirectory(int index,
ImageDataDirectory* directory) {
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_)
return Bad("Number of data directories inconsistent");
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.
static bool QuickDetect(const uint8_t* start, size_t length, uint16_t magic);
bool ParseAbs32Relocs();
void ParseRel32RelocsFromSections();
// Returns true if the given RVA range lies within [0, |size_of_image_|).
bool IsRvaRangeInBounds(size_t start, size_t length);
// Returns whether all sections lie within image.
bool CheckSectionRanges();
// Disassembler interfaces.
bool ExtractAbs32Locations() override;
@ -91,7 +94,7 @@ class DisassemblerWin32 : public Disassembler {
InstructionReceptor* receptor) const = 0;
// Returns true if type is recognized.
virtual bool SupportsRelTableType(int type) const = 0;
virtual uint16_t OffsetOfDataDirectories() const = 0;
virtual uint16_t RelativeOffsetOfDataDirectories() const = 0;
#if COURGETTE_HISTOGRAM_TARGETS
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;
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;
int number_of_data_directories_ = 0;

@ -44,7 +44,7 @@ class DisassemblerWin32X64 : public DisassemblerWin32 {
bool SupportsRelTableType(int type) const override {
return type == 10; // IMAGE_REL_BASED_DIR64
}
uint16_t OffsetOfDataDirectories() const override {
uint16_t RelativeOffsetOfDataDirectories() const override {
return kOffsetOfDataDirectoryFromImageOptionalHeader64;
}

@ -44,7 +44,7 @@ class DisassemblerWin32X86 : public DisassemblerWin32 {
bool SupportsRelTableType(int type) const override {
return type == 3; // IMAGE_REL_BASED_HIGHLOW
}
uint16_t OffsetOfDataDirectories() const override {
uint16_t RelativeOffsetOfDataDirectories() const override {
return kOffsetOfDataDirectoryFromImageOptionalHeader32;
}

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TYPES_WIN_PE_H_
#define TYPES_WIN_PE_H_
#ifndef COURGETTE_TYPES_WIN_PE_H_
#define COURGETTE_TYPES_WIN_PE_H_
#include <stddef.h>
#include <stdint.h>
@ -58,8 +58,10 @@ const uint16_t kImageNtOptionalHdr32Magic = 0x10b;
const uint16_t kImageNtOptionalHdr64Magic = 0x20b;
const size_t kSizeOfCoffHeader = 20;
const size_t kMinPeHeaderSize = 4 /*signature*/ + kSizeOfCoffHeader;
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader32 = 96;
const size_t kOffsetOfDataDirectoryFromImageOptionalHeader64 = 112;
} // namespace
#endif // TYPES_WIN_PE_H_
} // namespace courgette
#endif // COURGETTE_TYPES_WIN_PE_H_