OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "util/mac/mach_o_image_reader.h" |
| 16 |
| 17 #include <mach-o/loader.h> |
| 18 #include <mach-o/nlist.h> |
| 19 #include <string.h> |
| 20 |
| 21 #include <limits> |
| 22 #include <vector> |
| 23 |
| 24 #include "base/logging.h" |
| 25 #include "base/strings/stringprintf.h" |
| 26 #include "util/mac/checked_mach_address_range.h" |
| 27 #include "util/mac/mach_o_image_segment_reader.h" |
| 28 #include "util/mac/process_reader.h" |
| 29 |
| 30 namespace { |
| 31 |
| 32 const uint32_t kInvalidSegmentIndex = std::numeric_limits<uint32_t>::max(); |
| 33 |
| 34 } // namespace |
| 35 |
| 36 namespace crashpad { |
| 37 |
| 38 MachOImageReader::MachOImageReader() |
| 39 : segments_(), |
| 40 segment_map_(), |
| 41 module_info_(), |
| 42 dylinker_name_(), |
| 43 uuid_(), |
| 44 address_(0), |
| 45 size_(0), |
| 46 slide_(0), |
| 47 source_version_(0), |
| 48 symtab_command_(), |
| 49 dysymtab_command_(), |
| 50 id_dylib_command_(), |
| 51 process_reader_(NULL), |
| 52 file_type_(0), |
| 53 initialized_() { |
| 54 } |
| 55 |
| 56 MachOImageReader::~MachOImageReader() { |
| 57 } |
| 58 |
| 59 bool MachOImageReader::Initialize(ProcessReader* process_reader, |
| 60 mach_vm_address_t address, |
| 61 const std::string& name) { |
| 62 INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| 63 |
| 64 process_reader_ = process_reader; |
| 65 address_ = address; |
| 66 |
| 67 module_info_ = |
| 68 base::StringPrintf(", module %s, address 0x%llx", name.c_str(), address); |
| 69 |
| 70 process_types::mach_header mach_header; |
| 71 if (!mach_header.Read(process_reader, address)) { |
| 72 LOG(WARNING) << "could not read mach_header" << module_info_; |
| 73 return false; |
| 74 } |
| 75 |
| 76 const bool is_64_bit = process_reader->Is64Bit(); |
| 77 const uint32_t kExpectedMagic = is_64_bit ? MH_MAGIC_64 : MH_MAGIC; |
| 78 if (mach_header.magic != kExpectedMagic) { |
| 79 LOG(WARNING) << base::StringPrintf("unexpected mach_header::magic 0x%08x", |
| 80 mach_header.magic) << module_info_; |
| 81 return false; |
| 82 } |
| 83 |
| 84 file_type_ = mach_header.filetype; |
| 85 |
| 86 const uint32_t kExpectedSegmentCommand = |
| 87 is_64_bit ? LC_SEGMENT_64 : LC_SEGMENT; |
| 88 const uint32_t kUnexpectedSegmentCommand = |
| 89 is_64_bit ? LC_SEGMENT : LC_SEGMENT_64; |
| 90 |
| 91 const struct { |
| 92 // Which method to call when encountering a load command matching |command|. |
| 93 bool (MachOImageReader::*function)(mach_vm_address_t, const std::string&); |
| 94 |
| 95 // The minimum size that may be allotted to store the load command. |
| 96 size_t size; |
| 97 |
| 98 // The load command to match. |
| 99 uint32_t command; |
| 100 |
| 101 // True if the load command must not appear more than one time. |
| 102 bool singleton; |
| 103 } kLoadCommandReaders[] = { |
| 104 { |
| 105 &MachOImageReader::ReadSegmentCommand, |
| 106 process_types::segment_command::ExpectedSize(process_reader), |
| 107 kExpectedSegmentCommand, |
| 108 false, |
| 109 }, |
| 110 { |
| 111 &MachOImageReader::ReadSymTabCommand, |
| 112 process_types::symtab_command::ExpectedSize(process_reader), |
| 113 LC_SYMTAB, |
| 114 true, |
| 115 }, |
| 116 { |
| 117 &MachOImageReader::ReadDySymTabCommand, |
| 118 process_types::symtab_command::ExpectedSize(process_reader), |
| 119 LC_DYSYMTAB, |
| 120 true, |
| 121 }, |
| 122 { |
| 123 &MachOImageReader::ReadIdDylibCommand, |
| 124 process_types::dylib_command::ExpectedSize(process_reader), |
| 125 LC_ID_DYLIB, |
| 126 true, |
| 127 }, |
| 128 { |
| 129 &MachOImageReader::ReadDylinkerCommand, |
| 130 process_types::dylinker_command::ExpectedSize(process_reader), |
| 131 LC_LOAD_DYLINKER, |
| 132 true, |
| 133 }, |
| 134 { |
| 135 &MachOImageReader::ReadDylinkerCommand, |
| 136 process_types::dylinker_command::ExpectedSize(process_reader), |
| 137 LC_ID_DYLINKER, |
| 138 true, |
| 139 }, |
| 140 { |
| 141 &MachOImageReader::ReadUUIDCommand, |
| 142 process_types::uuid_command::ExpectedSize(process_reader), |
| 143 LC_UUID, |
| 144 true, |
| 145 }, |
| 146 { |
| 147 &MachOImageReader::ReadSourceVersionCommand, |
| 148 process_types::source_version_command::ExpectedSize(process_reader), |
| 149 LC_SOURCE_VERSION, |
| 150 true, |
| 151 }, |
| 152 |
| 153 // When reading a 64-bit process, no 32-bit segment commands should be |
| 154 // present, and vice-versa. |
| 155 { |
| 156 &MachOImageReader::ReadUnexpectedCommand, |
| 157 process_types::load_command::ExpectedSize(process_reader), |
| 158 kUnexpectedSegmentCommand, |
| 159 false, |
| 160 }, |
| 161 }; |
| 162 |
| 163 // This vector is parallel to the kLoadCommandReaders array, and tracks |
| 164 // whether a singleton load command matching the |command| field has been |
| 165 // found yet. |
| 166 std::vector<uint32_t> singleton_indices(arraysize(kLoadCommandReaders), |
| 167 kInvalidSegmentIndex); |
| 168 |
| 169 size_t offset = mach_header.Size(); |
| 170 const mach_vm_address_t kLoadCommandAddressLimit = |
| 171 address + offset + mach_header.sizeofcmds; |
| 172 |
| 173 for (uint32_t load_command_index = 0; |
| 174 load_command_index < mach_header.ncmds; |
| 175 ++load_command_index) { |
| 176 mach_vm_address_t load_command_address = address + offset; |
| 177 std::string load_command_info = base::StringPrintf(", load command %u/%u%s", |
| 178 load_command_index, |
| 179 mach_header.ncmds, |
| 180 module_info_.c_str()); |
| 181 |
| 182 process_types::load_command load_command; |
| 183 |
| 184 // Make sure that the basic load command structure doesn’t overflow the |
| 185 // space allotted for load commands. |
| 186 if (load_command_address + load_command.ExpectedSize(process_reader) > |
| 187 kLoadCommandAddressLimit) { |
| 188 LOG(WARNING) << base::StringPrintf( |
| 189 "load_command at 0x%llx exceeds sizeofcmds 0x%x", |
| 190 load_command_address, |
| 191 mach_header.sizeofcmds) << load_command_info; |
| 192 return false; |
| 193 } |
| 194 |
| 195 if (!load_command.Read(process_reader, load_command_address)) { |
| 196 LOG(WARNING) << "could not read load_command" << load_command_info; |
| 197 return false; |
| 198 } |
| 199 |
| 200 load_command_info = base::StringPrintf(", load command 0x%x %u/%u%s", |
| 201 load_command.cmd, |
| 202 load_command_index, |
| 203 mach_header.ncmds, |
| 204 module_info_.c_str()); |
| 205 |
| 206 // Now that the load command’s stated size is known, make sure that it |
| 207 // doesn’t overflow the space allotted for load commands. |
| 208 if (load_command_address + load_command.cmdsize > |
| 209 kLoadCommandAddressLimit) { |
| 210 LOG(WARNING) |
| 211 << base::StringPrintf( |
| 212 "load_command at 0x%llx cmdsize 0x%x exceeds sizeofcmds 0x%x", |
| 213 load_command_address, |
| 214 load_command.cmdsize, |
| 215 mach_header.sizeofcmds) << load_command_info; |
| 216 return false; |
| 217 } |
| 218 |
| 219 for (size_t reader_index = 0; |
| 220 reader_index < arraysize(kLoadCommandReaders); |
| 221 ++reader_index) { |
| 222 if (load_command.cmd != kLoadCommandReaders[reader_index].command) { |
| 223 continue; |
| 224 } |
| 225 |
| 226 if (load_command.cmdsize < kLoadCommandReaders[reader_index].size) { |
| 227 LOG(WARNING) |
| 228 << base::StringPrintf( |
| 229 "load command cmdsize 0x%x insufficient for 0x%zx", |
| 230 load_command.cmdsize, |
| 231 kLoadCommandReaders[reader_index].size) |
| 232 << load_command_info; |
| 233 return false; |
| 234 } |
| 235 |
| 236 if (kLoadCommandReaders[reader_index].singleton) { |
| 237 if (singleton_indices[reader_index] != kInvalidSegmentIndex) { |
| 238 LOG(WARNING) << "duplicate load command at " |
| 239 << singleton_indices[reader_index] |
| 240 << load_command_info; |
| 241 return false; |
| 242 } |
| 243 |
| 244 singleton_indices[reader_index] = load_command_index; |
| 245 } |
| 246 |
| 247 if (!((this)->*(kLoadCommandReaders[reader_index].function))( |
| 248 load_command_address, load_command_info)) { |
| 249 return false; |
| 250 } |
| 251 |
| 252 break; |
| 253 } |
| 254 |
| 255 offset += load_command.cmdsize; |
| 256 } |
| 257 |
| 258 // This was already checked for the unslid values while the segments were |
| 259 // read, but now that the slide is known, check the slid values too. The |
| 260 // individual sections don’t need to be checked because they were verified to |
| 261 // be contained within their respective segments when the segments were read. |
| 262 for (const MachOImageSegmentReader* segment : segments_) { |
| 263 mach_vm_address_t slid_segment_address = segment->vmaddr(); |
| 264 mach_vm_size_t slid_segment_size = segment->vmsize(); |
| 265 if (segment->SegmentSlides()) { |
| 266 slid_segment_address += slide_; |
| 267 } else { |
| 268 // The non-sliding __PAGEZERO segment extends instead of slides. See |
| 269 // MachOImageSegmentReader::SegmentSlides(). |
| 270 slid_segment_size += slide_; |
| 271 } |
| 272 CheckedMachAddressRange slid_segment_range( |
| 273 process_reader_, slid_segment_address, slid_segment_size); |
| 274 if (!slid_segment_range.IsValid()) { |
| 275 LOG(WARNING) << base::StringPrintf( |
| 276 "invalid slid segment range 0x%llx + 0x%llx, " |
| 277 "segment ", |
| 278 slid_segment_address, |
| 279 slid_segment_size) << segment->Name() << module_info_; |
| 280 return false; |
| 281 } |
| 282 } |
| 283 |
| 284 if (!segment_map_.count(SEG_TEXT)) { |
| 285 // The __TEXT segment is required. Even a module with no executable code |
| 286 // will have a __TEXT segment encompassing the Mach-O header and load |
| 287 // commands. Without a __TEXT segment, |size_| will not have been computed. |
| 288 LOG(WARNING) << "no " SEG_TEXT " segment" << module_info_; |
| 289 return false; |
| 290 } |
| 291 |
| 292 if (mach_header.filetype == MH_DYLIB && !id_dylib_command_) { |
| 293 // This doesn’t render a module unusable, it’s just weird and worth noting. |
| 294 LOG(INFO) << "no LC_ID_DYLIB" << module_info_; |
| 295 } |
| 296 |
| 297 INITIALIZATION_STATE_SET_VALID(initialized_); |
| 298 return true; |
| 299 } |
| 300 |
| 301 const MachOImageSegmentReader* MachOImageReader::GetSegmentByName( |
| 302 const std::string& segment_name, |
| 303 mach_vm_address_t* address, |
| 304 mach_vm_size_t* size) const { |
| 305 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 306 |
| 307 const auto& iterator = segment_map_.find(segment_name); |
| 308 if (iterator == segment_map_.end()) { |
| 309 return NULL; |
| 310 } |
| 311 |
| 312 const MachOImageSegmentReader* segment = segments_[iterator->second]; |
| 313 if (address) { |
| 314 *address = segment->vmaddr() + (segment->SegmentSlides() ? slide_ : 0); |
| 315 } |
| 316 if (size) { |
| 317 // The non-sliding __PAGEZERO segment extends instead of slides. See |
| 318 // MachOImageSegmentReader::SegmentSlides(). |
| 319 *size = segment->vmsize() + (segment->SegmentSlides() ? 0 : slide_); |
| 320 } |
| 321 |
| 322 return segment; |
| 323 } |
| 324 |
| 325 const process_types::section* MachOImageReader::GetSectionByName( |
| 326 const std::string& segment_name, |
| 327 const std::string& section_name, |
| 328 mach_vm_address_t* address) const { |
| 329 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 330 |
| 331 const MachOImageSegmentReader* segment = |
| 332 GetSegmentByName(segment_name, NULL, NULL); |
| 333 if (!segment) { |
| 334 return NULL; |
| 335 } |
| 336 |
| 337 const process_types::section* section = |
| 338 segment->GetSectionByName(section_name); |
| 339 if (!section) { |
| 340 return NULL; |
| 341 } |
| 342 |
| 343 if (address) { |
| 344 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0); |
| 345 } |
| 346 |
| 347 return section; |
| 348 } |
| 349 |
| 350 const process_types::section* MachOImageReader::GetSectionAtIndex( |
| 351 size_t index, |
| 352 mach_vm_address_t* address) const { |
| 353 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 354 |
| 355 COMPILE_ASSERT(NO_SECT == 0, no_sect_must_be_zero); |
| 356 if (index == NO_SECT) { |
| 357 LOG(WARNING) << "section index " << index << " out of range"; |
| 358 return NULL; |
| 359 } |
| 360 |
| 361 // Switch to a more comfortable 0-based index. |
| 362 size_t local_index = index - 1; |
| 363 |
| 364 for (const MachOImageSegmentReader* segment : segments_) { |
| 365 size_t nsects = segment->nsects(); |
| 366 if (local_index < nsects) { |
| 367 const process_types::section* section = |
| 368 segment->GetSectionAtIndex(local_index); |
| 369 |
| 370 if (address) { |
| 371 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0); |
| 372 } |
| 373 |
| 374 return section; |
| 375 } |
| 376 |
| 377 local_index -= nsects; |
| 378 } |
| 379 |
| 380 LOG(WARNING) << "section index " << index << " out of range"; |
| 381 return NULL; |
| 382 } |
| 383 |
| 384 uint32_t MachOImageReader::DylibVersion() const { |
| 385 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 386 DCHECK_EQ(FileType(), static_cast<uint32_t>(MH_DYLIB)); |
| 387 |
| 388 if (id_dylib_command_) { |
| 389 return id_dylib_command_->dylib_current_version; |
| 390 } |
| 391 |
| 392 // In case this was a weird dylib without an LC_ID_DYLIB command. |
| 393 return 0; |
| 394 } |
| 395 |
| 396 void MachOImageReader::UUID(crashpad::UUID* uuid) const { |
| 397 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 398 memcpy(uuid, &uuid_, sizeof(uuid_)); |
| 399 } |
| 400 |
| 401 template <typename T> |
| 402 bool MachOImageReader::ReadLoadCommand(mach_vm_address_t load_command_address, |
| 403 const std::string& load_command_info, |
| 404 uint32_t expected_load_command_id, |
| 405 T* load_command) { |
| 406 if (!load_command->Read(process_reader_, load_command_address)) { |
| 407 LOG(WARNING) << "could not read load command" << load_command_info; |
| 408 return false; |
| 409 } |
| 410 |
| 411 DCHECK_GE(load_command->cmdsize, load_command->Size()); |
| 412 DCHECK_EQ(load_command->cmd, expected_load_command_id); |
| 413 return true; |
| 414 } |
| 415 |
| 416 bool MachOImageReader::ReadSegmentCommand( |
| 417 mach_vm_address_t load_command_address, |
| 418 const std::string& load_command_info) { |
| 419 MachOImageSegmentReader* segment = new MachOImageSegmentReader(); |
| 420 size_t segment_index = segments_.size(); |
| 421 segments_.push_back(segment); // Takes ownership. |
| 422 |
| 423 if (!segment->Initialize( |
| 424 process_reader_, load_command_address, load_command_info)) { |
| 425 segments_.pop_back(); |
| 426 return false; |
| 427 } |
| 428 |
| 429 // At this point, the segment itself is considered valid, but if one of the |
| 430 // next checks fails, it will render the module invalid. If any of the next |
| 431 // checks fail, this method should return false, but it doesn’t need to bother |
| 432 // removing the segment from segments_. The segment will be properly released |
| 433 // when the image is destroyed, and the image won’t be usable because |
| 434 // initialization won’t have completed. Most importantly, leaving the segment |
| 435 // in segments_ means that no other structures (such as perhaps segment_map_) |
| 436 // become inconsistent or require cleanup. |
| 437 |
| 438 const std::string segment_name = segment->Name(); |
| 439 const auto& iterator = segment_map_.find(segment_name); |
| 440 if (iterator != segment_map_.end()) { |
| 441 LOG(WARNING) << base::StringPrintf("duplicate %s segment at %zu and %zu", |
| 442 segment_name.c_str(), |
| 443 iterator->second, |
| 444 segment_index) << load_command_info; |
| 445 return false; |
| 446 } |
| 447 segment_map_[segment_name] = segment_index; |
| 448 |
| 449 mach_vm_size_t vmsize = segment->vmsize(); |
| 450 |
| 451 if (segment_name == SEG_TEXT) { |
| 452 if (vmsize == 0) { |
| 453 LOG(WARNING) << "zero-sized " SEG_TEXT " segment" << load_command_info; |
| 454 return false; |
| 455 } |
| 456 |
| 457 mach_vm_size_t fileoff = segment->fileoff(); |
| 458 if (fileoff != 0) { |
| 459 LOG(WARNING) << base::StringPrintf( |
| 460 SEG_TEXT " segment has unexpected fileoff 0x%llx", |
| 461 fileoff) << load_command_info; |
| 462 return false; |
| 463 } |
| 464 |
| 465 size_ = vmsize; |
| 466 |
| 467 // The slide is computed as the difference between the __TEXT segment’s |
| 468 // preferred and actual load addresses. This is the same way that dyld |
| 469 // computes slide. See 10.9.2 dyld-239.4/src/dyldInitialization.cpp |
| 470 // slideOfMainExecutable(). |
| 471 slide_ = address_ - segment->vmaddr(); |
| 472 } |
| 473 |
| 474 return true; |
| 475 } |
| 476 |
| 477 bool MachOImageReader::ReadSymTabCommand(mach_vm_address_t load_command_address, |
| 478 const std::string& load_command_info) { |
| 479 symtab_command_.reset(new process_types::symtab_command()); |
| 480 return ReadLoadCommand(load_command_address, |
| 481 load_command_info, |
| 482 LC_SYMTAB, |
| 483 symtab_command_.get()); |
| 484 } |
| 485 |
| 486 bool MachOImageReader::ReadDySymTabCommand( |
| 487 mach_vm_address_t load_command_address, |
| 488 const std::string& load_command_info) { |
| 489 dysymtab_command_.reset(new process_types::dysymtab_command()); |
| 490 return ReadLoadCommand(load_command_address, |
| 491 load_command_info, |
| 492 LC_DYSYMTAB, |
| 493 dysymtab_command_.get()); |
| 494 } |
| 495 |
| 496 bool MachOImageReader::ReadIdDylibCommand( |
| 497 mach_vm_address_t load_command_address, |
| 498 const std::string& load_command_info) { |
| 499 if (file_type_ != MH_DYLIB) { |
| 500 LOG(WARNING) << base::StringPrintf( |
| 501 "LC_ID_DYLIB inappropriate in non-dylib file type 0x%x", |
| 502 file_type_) << load_command_info; |
| 503 return false; |
| 504 } |
| 505 |
| 506 DCHECK(!id_dylib_command_); |
| 507 id_dylib_command_.reset(new process_types::dylib_command()); |
| 508 return ReadLoadCommand(load_command_address, |
| 509 load_command_info, |
| 510 LC_ID_DYLIB, |
| 511 id_dylib_command_.get()); |
| 512 } |
| 513 |
| 514 bool MachOImageReader::ReadDylinkerCommand( |
| 515 mach_vm_address_t load_command_address, |
| 516 const std::string& load_command_info) { |
| 517 if (file_type_ != MH_EXECUTE && file_type_ != MH_DYLINKER) { |
| 518 LOG(WARNING) << base::StringPrintf( |
| 519 "LC_LOAD_DYLINKER/LC_ID_DYLINKER inappropriate in file " |
| 520 "type 0x%x", |
| 521 file_type_) << load_command_info; |
| 522 return false; |
| 523 } |
| 524 |
| 525 const uint32_t kExpectedCommand = |
| 526 file_type_ == MH_DYLINKER ? LC_ID_DYLINKER : LC_LOAD_DYLINKER; |
| 527 process_types::dylinker_command dylinker_command; |
| 528 if (!ReadLoadCommand(load_command_address, |
| 529 load_command_info, |
| 530 kExpectedCommand, |
| 531 &dylinker_command)) { |
| 532 return false; |
| 533 } |
| 534 |
| 535 if (!process_reader_->Memory()->ReadCStringSizeLimited( |
| 536 load_command_address + dylinker_command.name, |
| 537 dylinker_command.cmdsize - dylinker_command.name, |
| 538 &dylinker_name_)) { |
| 539 LOG(WARNING) << "could not read dylinker_command name" << load_command_info; |
| 540 return false; |
| 541 } |
| 542 |
| 543 return true; |
| 544 } |
| 545 |
| 546 bool MachOImageReader::ReadUUIDCommand(mach_vm_address_t load_command_address, |
| 547 const std::string& load_command_info) { |
| 548 process_types::uuid_command uuid_command; |
| 549 if (!ReadLoadCommand( |
| 550 load_command_address, load_command_info, LC_UUID, &uuid_command)) { |
| 551 return false; |
| 552 } |
| 553 |
| 554 uuid_.InitializeFromBytes(uuid_command.uuid); |
| 555 return true; |
| 556 } |
| 557 |
| 558 bool MachOImageReader::ReadSourceVersionCommand( |
| 559 mach_vm_address_t load_command_address, |
| 560 const std::string& load_command_info) { |
| 561 process_types::source_version_command source_version_command; |
| 562 if (!ReadLoadCommand(load_command_address, |
| 563 load_command_info, |
| 564 LC_SOURCE_VERSION, |
| 565 &source_version_command)) { |
| 566 return false; |
| 567 } |
| 568 |
| 569 source_version_ = source_version_command.version; |
| 570 return true; |
| 571 } |
| 572 |
| 573 bool MachOImageReader::ReadUnexpectedCommand( |
| 574 mach_vm_address_t load_command_address, |
| 575 const std::string& load_command_info) { |
| 576 LOG(WARNING) << "unexpected load command" << load_command_info; |
| 577 return false; |
| 578 } |
| 579 |
| 580 } // namespace crashpad |
OLD | NEW |