• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/win/pe_image_reader.h"
11 
12 #include <wintrust.h>
13 
14 #include <memory>
15 
16 #include "base/check_op.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/numerics/safe_math.h"
19 
20 namespace base {
21 namespace win {
22 
23 // A class template of traits pertaining to IMAGE_OPTIONAL_HEADER{32,64}.
24 template <class HEADER_TYPE>
25 struct OptionalHeaderTraits {};
26 
27 template <>
28 struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER32> {
29   static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_32;
30 };
31 
32 template <>
33 struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER64> {
34   static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_64;
35 };
36 
37 // A template for type-specific optional header implementations. This, in
38 // conjunction with the OptionalHeader interface, effectively erases the
39 // underlying structure type from the point of view of the PeImageReader.
40 template <class OPTIONAL_HEADER_TYPE>
41 class PeImageReader::OptionalHeaderImpl : public PeImageReader::OptionalHeader {
42  public:
43   using TraitsType = OptionalHeaderTraits<OPTIONAL_HEADER_TYPE>;
44 
OptionalHeaderImpl(const uint8_t * optional_header_start)45   explicit OptionalHeaderImpl(const uint8_t* optional_header_start)
46       : optional_header_(reinterpret_cast<const OPTIONAL_HEADER_TYPE*>(
47             optional_header_start)) {}
48 
49   OptionalHeaderImpl(const OptionalHeaderImpl&) = delete;
50   OptionalHeaderImpl& operator=(const OptionalHeaderImpl&) = delete;
51 
GetWordSize()52   WordSize GetWordSize() override { return TraitsType::word_size; }
53 
GetDataDirectoryOffset()54   size_t GetDataDirectoryOffset() override {
55     return offsetof(OPTIONAL_HEADER_TYPE, DataDirectory);
56   }
57 
GetDataDirectorySize()58   DWORD GetDataDirectorySize() override {
59     return optional_header_->NumberOfRvaAndSizes;
60   }
61 
GetDataDirectoryEntries()62   const IMAGE_DATA_DIRECTORY* GetDataDirectoryEntries() override {
63     return &optional_header_->DataDirectory[0];
64   }
65 
GetSizeOfImage()66   DWORD GetSizeOfImage() override { return optional_header_->SizeOfImage; }
67 
68  private:
69   raw_ptr<const OPTIONAL_HEADER_TYPE> optional_header_;
70 };
71 
72 PeImageReader::PeImageReader() = default;
73 
~PeImageReader()74 PeImageReader::~PeImageReader() {
75   Clear();
76 }
77 
Initialize(span<const uint8_t> image_data)78 bool PeImageReader::Initialize(span<const uint8_t> image_data) {
79   image_data_ = image_data;
80 
81   if (!ValidateDosHeader() || !ValidatePeSignature() ||
82       !ValidateCoffFileHeader() || !ValidateOptionalHeader() ||
83       !ValidateSectionHeaders()) {
84     Clear();
85     return false;
86   }
87 
88   return true;
89 }
90 
GetWordSize()91 PeImageReader::WordSize PeImageReader::GetWordSize() {
92   return optional_header_->GetWordSize();
93 }
94 
GetDosHeader()95 const IMAGE_DOS_HEADER* PeImageReader::GetDosHeader() {
96   DCHECK_NE((validation_state_ & VALID_DOS_HEADER), 0U);
97   return reinterpret_cast<const IMAGE_DOS_HEADER*>(image_data_.data());
98 }
99 
GetCoffFileHeader()100 const IMAGE_FILE_HEADER* PeImageReader::GetCoffFileHeader() {
101   DCHECK_NE((validation_state_ & VALID_COFF_FILE_HEADER), 0U);
102   return reinterpret_cast<const IMAGE_FILE_HEADER*>(
103       image_data_
104           .subspan(checked_cast<size_t>(GetDosHeader()->e_lfanew) +
105                    sizeof(DWORD))
106           .data());
107 }
108 
GetOptionalHeaderData()109 span<const uint8_t> PeImageReader::GetOptionalHeaderData() {
110   return span(GetOptionalHeaderStart(), GetOptionalHeaderSize());
111 }
112 
GetNumberOfSections()113 size_t PeImageReader::GetNumberOfSections() {
114   return GetCoffFileHeader()->NumberOfSections;
115 }
116 
GetSectionHeaderAt(size_t index)117 const IMAGE_SECTION_HEADER* PeImageReader::GetSectionHeaderAt(size_t index) {
118   DCHECK_NE((validation_state_ & VALID_SECTION_HEADERS), 0U);
119   DCHECK_LT(index, GetNumberOfSections());
120   return reinterpret_cast<const IMAGE_SECTION_HEADER*>(
121       GetOptionalHeaderStart() + GetOptionalHeaderSize() +
122       (sizeof(IMAGE_SECTION_HEADER) * index));
123 }
124 
GetExportSection()125 span<const uint8_t> PeImageReader::GetExportSection() {
126   span<const uint8_t> data = GetImageData(IMAGE_DIRECTORY_ENTRY_EXPORT);
127 
128   // The export section data must be big enough for the export directory.
129   if (data.size() < sizeof(IMAGE_EXPORT_DIRECTORY)) {
130     return span<const uint8_t>();
131   }
132 
133   return data;
134 }
135 
GetNumberOfDebugEntries()136 size_t PeImageReader::GetNumberOfDebugEntries() {
137   return GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG).size() /
138          sizeof(IMAGE_DEBUG_DIRECTORY);
139 }
140 
GetDebugEntry(size_t index,span<const uint8_t> & raw_data)141 const IMAGE_DEBUG_DIRECTORY* PeImageReader::GetDebugEntry(
142     size_t index,
143     span<const uint8_t>& raw_data) {
144   DCHECK_LT(index, GetNumberOfDebugEntries());
145 
146   // Get the debug directory.
147   span<const uint8_t> debug_directory_data =
148       GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG);
149   if (debug_directory_data.empty()) {
150     return nullptr;
151   }
152 
153   const IMAGE_DEBUG_DIRECTORY& entry =
154       reinterpret_cast<const IMAGE_DEBUG_DIRECTORY&>(
155           debug_directory_data[index * sizeof(IMAGE_DEBUG_DIRECTORY)]);
156   const uint8_t* debug_data = nullptr;
157   if (GetStructureAt(entry.PointerToRawData, entry.SizeOfData, &debug_data)) {
158     raw_data = span(debug_data, entry.SizeOfData);
159   }
160   return &entry;
161 }
162 
EnumCertificates(EnumCertificatesCallback callback,void * context)163 bool PeImageReader::EnumCertificates(EnumCertificatesCallback callback,
164                                      void* context) {
165   span<const uint8_t> data = GetImageData(IMAGE_DIRECTORY_ENTRY_SECURITY);
166   const size_t kWinCertificateSize = offsetof(WIN_CERTIFICATE, bCertificate);
167   while (!data.empty()) {
168     const WIN_CERTIFICATE* win_certificate =
169         reinterpret_cast<const WIN_CERTIFICATE*>(data.data());
170     if (kWinCertificateSize > data.size() ||
171         kWinCertificateSize > win_certificate->dwLength ||
172         win_certificate->dwLength > data.size()) {
173       return false;
174     }
175     if (!(*callback)(
176             win_certificate->wRevision, win_certificate->wCertificateType,
177             base::span(&win_certificate->bCertificate[0],
178                        win_certificate->dwLength - kWinCertificateSize),
179             context)) {
180       return false;
181     }
182     size_t padded_length = (win_certificate->dwLength + 7) & ~0x7u;
183     if (padded_length > data.size()) {
184       return false;
185     }
186     data = data.subspan(padded_length);
187   }
188   return true;
189 }
190 
GetSizeOfImage()191 DWORD PeImageReader::GetSizeOfImage() {
192   return optional_header_->GetSizeOfImage();
193 }
194 
Clear()195 void PeImageReader::Clear() {
196   image_data_ = raw_span<const uint8_t>();
197   validation_state_ = 0;
198   optional_header_.reset();
199 }
200 
ValidateDosHeader()201 bool PeImageReader::ValidateDosHeader() {
202   const IMAGE_DOS_HEADER* dos_header = nullptr;
203   if (!GetStructureAt(0, &dos_header) ||
204       dos_header->e_magic != IMAGE_DOS_SIGNATURE || dos_header->e_lfanew < 0) {
205     return false;
206   }
207 
208   validation_state_ |= VALID_DOS_HEADER;
209   return true;
210 }
211 
ValidatePeSignature()212 bool PeImageReader::ValidatePeSignature() {
213   const DWORD* signature = nullptr;
214   if (!GetStructureAt(static_cast<size_t>(GetDosHeader()->e_lfanew),
215                       &signature) ||
216       *signature != IMAGE_NT_SIGNATURE) {
217     return false;
218   }
219 
220   validation_state_ |= VALID_PE_SIGNATURE;
221   return true;
222 }
223 
ValidateCoffFileHeader()224 bool PeImageReader::ValidateCoffFileHeader() {
225   DCHECK_NE((validation_state_ & VALID_PE_SIGNATURE), 0U);
226   const IMAGE_FILE_HEADER* file_header = nullptr;
227   if (!GetStructureAt(static_cast<size_t>(GetDosHeader()->e_lfanew) +
228                           offsetof(IMAGE_NT_HEADERS32, FileHeader),
229                       &file_header)) {
230     return false;
231   }
232 
233   validation_state_ |= VALID_COFF_FILE_HEADER;
234   return true;
235 }
236 
ValidateOptionalHeader()237 bool PeImageReader::ValidateOptionalHeader() {
238   const IMAGE_FILE_HEADER* file_header = GetCoffFileHeader();
239   const size_t optional_header_offset =
240       static_cast<size_t>(GetDosHeader()->e_lfanew) +
241       offsetof(IMAGE_NT_HEADERS32, OptionalHeader);
242   const size_t optional_header_size = file_header->SizeOfOptionalHeader;
243   const WORD* optional_header_magic = nullptr;
244 
245   if (optional_header_size < sizeof(*optional_header_magic) ||
246       !GetStructureAt(optional_header_offset, &optional_header_magic)) {
247     return false;
248   }
249 
250   std::unique_ptr<OptionalHeader> optional_header;
251   if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
252     optional_header =
253         std::make_unique<OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER32>>(
254             image_data_.subspan(optional_header_offset).data());
255   } else if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
256     optional_header =
257         std::make_unique<OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER64>>(
258             image_data_.subspan(optional_header_offset).data());
259   } else {
260     return false;
261   }
262 
263   // Does all of the claimed optional header fit in the image?
264   if (optional_header_size > image_data_.size() - optional_header_offset) {
265     return false;
266   }
267 
268   // Is the claimed optional header big enough for everything but the dir?
269   if (optional_header->GetDataDirectoryOffset() > optional_header_size)
270     return false;
271 
272   // Is there enough room for all of the claimed directory entries?
273   if (optional_header->GetDataDirectorySize() >
274       ((optional_header_size - optional_header->GetDataDirectoryOffset()) /
275        sizeof(IMAGE_DATA_DIRECTORY))) {
276     return false;
277   }
278 
279   optional_header_.swap(optional_header);
280   validation_state_ |= VALID_OPTIONAL_HEADER;
281   return true;
282 }
283 
ValidateSectionHeaders()284 bool PeImageReader::ValidateSectionHeaders() {
285   const uint8_t* first_section_header =
286       GetOptionalHeaderStart() + GetOptionalHeaderSize();
287   const size_t number_of_sections = GetNumberOfSections();
288 
289   // Do all section headers fit in the image?
290   if (!GetStructureAt(
291           static_cast<size_t>(first_section_header - image_data_.data()),
292           number_of_sections * sizeof(IMAGE_SECTION_HEADER),
293           &first_section_header)) {
294     return false;
295   }
296 
297   validation_state_ |= VALID_SECTION_HEADERS;
298   return true;
299 }
300 
GetOptionalHeaderStart()301 const uint8_t* PeImageReader::GetOptionalHeaderStart() {
302   DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
303   return image_data_
304       .subspan(checked_cast<size_t>(GetDosHeader()->e_lfanew) +
305                offsetof(IMAGE_NT_HEADERS32, OptionalHeader))
306       .data();
307 }
308 
GetOptionalHeaderSize()309 size_t PeImageReader::GetOptionalHeaderSize() {
310   return GetCoffFileHeader()->SizeOfOptionalHeader;
311 }
312 
GetDataDirectoryEntryAt(size_t index)313 const IMAGE_DATA_DIRECTORY* PeImageReader::GetDataDirectoryEntryAt(
314     size_t index) {
315   DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
316   if (index >= optional_header_->GetDataDirectorySize())
317     return nullptr;
318   return &optional_header_->GetDataDirectoryEntries()[index];
319 }
320 
FindSectionFromRva(uint32_t relative_address)321 const IMAGE_SECTION_HEADER* PeImageReader::FindSectionFromRva(
322     uint32_t relative_address) {
323   const size_t number_of_sections = GetNumberOfSections();
324   for (size_t i = 0; i < number_of_sections; ++i) {
325     const IMAGE_SECTION_HEADER* section_header = GetSectionHeaderAt(i);
326     // Is the raw data present in the image? If no, optimistically keep looking.
327     const uint8_t* section_data = nullptr;
328     if (!GetStructureAt(section_header->PointerToRawData,
329                         section_header->SizeOfRawData, &section_data)) {
330       continue;
331     }
332     // Does the RVA lie on or after this section's start when mapped? If no,
333     // bail.
334     if (section_header->VirtualAddress > relative_address)
335       break;
336     // Does the RVA lie within the section when mapped? If no, keep looking.
337     size_t address_offset = relative_address - section_header->VirtualAddress;
338     if (address_offset > section_header->Misc.VirtualSize)
339       continue;
340     // We have a winner.
341     return section_header;
342   }
343   return nullptr;
344 }
345 
GetImageData(size_t index)346 span<const uint8_t> PeImageReader::GetImageData(size_t index) {
347   // Get the requested directory entry.
348   const IMAGE_DATA_DIRECTORY* entry = GetDataDirectoryEntryAt(index);
349   if (!entry)
350     return span<const uint8_t>();
351 
352   // The entry for the certificate table is special in that its address is a
353   // file pointer rather than an RVA.
354   if (index == IMAGE_DIRECTORY_ENTRY_SECURITY) {
355     // Does the data fit within the file.
356     if (entry->VirtualAddress > image_data_.size() ||
357         image_data_.size() - entry->VirtualAddress < entry->Size) {
358       return span<const uint8_t>();
359     }
360     return image_data_.subspan(entry->VirtualAddress, entry->Size);
361   }
362 
363   // Find the section containing the data.
364   const IMAGE_SECTION_HEADER* header =
365       FindSectionFromRva(entry->VirtualAddress);
366   if (!header)
367     return span<const uint8_t>();
368 
369   // Does the data fit within the section when mapped?
370   size_t data_offset = entry->VirtualAddress - header->VirtualAddress;
371   if (entry->Size > (header->Misc.VirtualSize - data_offset))
372     return span<const uint8_t>();
373 
374   // Is the data entirely present on disk (if not it's zeroed out when loaded)?
375   if (data_offset >= header->SizeOfRawData ||
376       header->SizeOfRawData - data_offset < entry->Size) {
377     return span<const uint8_t>();
378   }
379 
380   return image_data_.subspan(header->PointerToRawData + data_offset,
381                              entry->Size);
382 }
383 
384 }  // namespace win
385 }  // namespace base
386