diff --git a/courgette/disassembler_win32.cc b/courgette/disassembler_win32.cc index b89db4507425c..1592c219f8a86 100644 --- a/courgette/disassembler_win32.cc +++ b/courgette/disassembler_win32.cc @@ -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 = §ions_[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 = §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; } @@ -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; diff --git a/courgette/disassembler_win32.h b/courgette/disassembler_win32.h index d2df58c08df60..da6a16d325531 100644 --- a/courgette/disassembler_win32.h +++ b/courgette/disassembler_win32.h @@ -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; diff --git a/courgette/disassembler_win32_x64.h b/courgette/disassembler_win32_x64.h index a5e8317262de3..837c7455863a2 100644 --- a/courgette/disassembler_win32_x64.h +++ b/courgette/disassembler_win32_x64.h @@ -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; } diff --git a/courgette/disassembler_win32_x86.h b/courgette/disassembler_win32_x86.h index f747ef53e9944..84813e660f905 100644 --- a/courgette/disassembler_win32_x86.h +++ b/courgette/disassembler_win32_x86.h @@ -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; } diff --git a/courgette/types_win_pe.h b/courgette/types_win_pe.h index f926b5f65c55e..23aecf886ab3a 100644 --- a/courgette/types_win_pe.h +++ b/courgette/types_win_pe.h @@ -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_