// Copyright 2014 The Crashpad Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "snapshot/mac/mach_o_image_segment_reader.h" #include #include #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "snapshot/mac/process_reader_mac.h" #include "util/mac/checked_mach_address_range.h" #include "util/mac/mac_util.h" #include "util/stdlib/strnlen.h" namespace crashpad { namespace { std::string SizeLimitedCString(const char* c_string, size_t max_length) { return std::string(c_string, strnlen(c_string, max_length)); } } // namespace bool IsMalformedCLKernelsModule(uint32_t mach_o_file_type, const std::string& module_name, bool* has_timestamp) { #if defined(ARCH_CPU_X86_FAMILY) if (mach_o_file_type != MH_BUNDLE) { return false; } if (module_name == "cl_kernels") { if (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10 || MacOSVersionNumber() >= 10'10'00) { if (has_timestamp) { *has_timestamp = false; } return true; } return false; } static const char kCvmsObjectPathPrefix[] = "/private/var/db/CVMS/cvmsCodeSignObj"; if (module_name.compare( 0, strlen(kCvmsObjectPathPrefix), kCvmsObjectPathPrefix) == 0 && (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_14 || MacOSVersionNumber() >= 10'14'00)) { if (has_timestamp) { *has_timestamp = true; } return true; } #endif // ARCH_CPU_X86_FAMILY return false; } MachOImageSegmentReader::MachOImageSegmentReader() : segment_command_(), sections_(), section_map_(), slide_(0), initialized_(), initialized_slide_() { } MachOImageSegmentReader::~MachOImageSegmentReader() { } bool MachOImageSegmentReader::Initialize(ProcessReaderMac* process_reader, mach_vm_address_t load_command_address, const std::string& load_command_info, const std::string& module_name, uint32_t file_type) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (!segment_command_.Read(process_reader, load_command_address)) { LOG(WARNING) << "could not read segment_command" << load_command_info; return false; } const uint32_t kExpectedSegmentCommand = process_reader->Is64Bit() ? LC_SEGMENT_64 : LC_SEGMENT; DCHECK_EQ(segment_command_.cmd, kExpectedSegmentCommand); DCHECK_GE(segment_command_.cmdsize, segment_command_.Size()); const size_t kSectionStructSize = process_types::section::ExpectedSize(process_reader); const size_t kRequiredSize = segment_command_.Size() + segment_command_.nsects * kSectionStructSize; if (segment_command_.cmdsize < kRequiredSize) { LOG(WARNING) << base::StringPrintf( "segment command cmdsize 0x%x insufficient for %u " "section%s (0x%zx)", segment_command_.cmdsize, segment_command_.nsects, segment_command_.nsects == 1 ? "" : "s", kRequiredSize) << load_command_info; return false; } std::string segment_name = NameInternal(); std::string segment_info = base::StringPrintf( ", segment %s%s", segment_name.c_str(), load_command_info.c_str()); // This checks the unslid segment range. The slid range (as loaded into // memory) will be checked later by MachOImageReader. CheckedMachAddressRange segment_range(process_reader->Is64Bit(), segment_command_.vmaddr, segment_command_.vmsize); if (!segment_range.IsValid()) { LOG(WARNING) << base::StringPrintf("invalid segment range 0x%llx + 0x%llx", segment_command_.vmaddr, segment_command_.vmsize) << segment_info; return false; } sections_.resize(segment_command_.nsects); if (!sections_.empty() && !process_types::section::ReadArrayInto( process_reader, load_command_address + segment_command_.Size(), segment_command_.nsects, §ions_[0])) { LOG(WARNING) << "could not read sections" << segment_info; return false; } for (size_t section_index = 0; section_index < sections_.size(); ++section_index) { const process_types::section& section = sections_[section_index]; std::string section_segment_name = SegmentNameString(section.segname); std::string section_name = SectionNameString(section.sectname); std::string section_full_name = SegmentAndSectionNameString(section.segname, section.sectname); std::string section_info = base::StringPrintf(", section %s %zu/%zu%s", section_full_name.c_str(), section_index, sections_.size(), load_command_info.c_str()); // cl_kernels modules (for OpenCL) aren’t ld output, and they’re formatted // incorrectly on OS X 10.10 and later. Because at least one cl_kernels // module will commonly be found in a process, and sometimes more will be, // tolerate this quirk. // // https://openradar.appspot.com/20239912 if (section_segment_name != segment_name && !(IsMalformedCLKernelsModule(file_type, module_name, nullptr) && segment_name == SEG_TEXT && section_segment_name == "__LD" && section_name == "__compact_unwind" && (section.flags & S_ATTR_DEBUG))) { LOG(WARNING) << "section.segname incorrect in segment " << segment_name << section_info; return false; } CheckedMachAddressRange section_range( process_reader->Is64Bit(), section.addr, section.size); if (!section_range.IsValid()) { LOG(WARNING) << base::StringPrintf( "invalid section range 0x%llx + 0x%llx", section.addr, section.size) << section_info; return false; } if (!segment_range.ContainsRange(section_range)) { LOG(WARNING) << base::StringPrintf( "section at 0x%llx + 0x%llx outside of segment at " "0x%llx + 0x%llx", section.addr, section.size, segment_command_.vmaddr, segment_command_.vmsize) << section_info; return false; } const auto insert_result = section_map_.insert(std::make_pair(section_name, section_index)); if (!insert_result.second) { LOG(WARNING) << base::StringPrintf("duplicate section name at %zu", insert_result.first->second) << section_info; return false; } } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } std::string MachOImageSegmentReader::Name() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return NameInternal(); } mach_vm_address_t MachOImageSegmentReader::Address() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); INITIALIZATION_STATE_DCHECK_VALID(initialized_slide_); return vmaddr() + (SegmentSlides() ? slide_ : 0); } mach_vm_size_t MachOImageSegmentReader::Size() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); INITIALIZATION_STATE_DCHECK_VALID(initialized_slide_); return vmsize() + (SegmentSlides() ? 0 : slide_); } const process_types::section* MachOImageSegmentReader::GetSectionByName( const std::string& section_name, mach_vm_address_t* address) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); const auto& iterator = section_map_.find(section_name); if (iterator == section_map_.end()) { return nullptr; } return GetSectionAtIndex(iterator->second, address); } const process_types::section* MachOImageSegmentReader::GetSectionAtIndex( size_t index, mach_vm_address_t* address) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); CHECK_LT(index, sections_.size()); const process_types::section* section = §ions_[index]; if (address) { INITIALIZATION_STATE_DCHECK_VALID(initialized_slide_); *address = section->addr + (SegmentSlides() ? slide_ : 0); } return section; } bool MachOImageSegmentReader::SegmentSlides() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // These are the same rules that the kernel uses to identify __PAGEZERO. See // 10.9.4 xnu-2422.110.17/bsd/kern/mach_loader.c load_segment(). return !(segment_command_.vmaddr == 0 && segment_command_.filesize == 0 && segment_command_.vmsize != 0 && (segment_command_.initprot & VM_PROT_ALL) == VM_PROT_NONE && (segment_command_.maxprot & VM_PROT_ALL) == VM_PROT_NONE); } // static std::string MachOImageSegmentReader::SegmentNameString( const char* segment_name_c) { // This is used to interpret the segname field of both the segment_command and // section structures, so be sure that they’re identical. static_assert(sizeof(process_types::segment_command::segname) == sizeof(process_types::section::segname), "sizes must be equal"); return SizeLimitedCString(segment_name_c, sizeof(process_types::segment_command::segname)); } // static std::string MachOImageSegmentReader::SectionNameString( const char* section_name_c) { return SizeLimitedCString(section_name_c, sizeof(process_types::section::sectname)); } // static std::string MachOImageSegmentReader::SegmentAndSectionNameString( const char* segment_name_c, const char* section_name_c) { return base::StringPrintf("%s,%s", SegmentNameString(segment_name_c).c_str(), SectionNameString(section_name_c).c_str()); } std::string MachOImageSegmentReader::NameInternal() const { return SegmentNameString(segment_command_.segname); } void MachOImageSegmentReader::SetSlide(mach_vm_size_t slide) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); INITIALIZATION_STATE_SET_INITIALIZING(initialized_slide_); slide_ = slide; INITIALIZATION_STATE_SET_VALID(initialized_slide_); } } // namespace crashpad