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 "snapshot/mac/mach_o_image_symbol_table_reader.h"
16 
17 #include <mach-o/loader.h>
18 #include <mach-o/nlist.h>
19 #include <sys/types.h>
20 
21 #include <memory>
22 #include <utility>
23 
24 #include "base/logging.h"
25 #include "base/strings/stringprintf.h"
26 #include "util/mac/checked_mach_address_range.h"
27 #include "util/process/process_memory_mac.h"
28 
29 namespace crashpad {
30 
31 namespace internal {
32 
33 //! \brief The internal implementation for MachOImageSymbolTableReader.
34 //!
35 //! Initialization is broken into more than one function that needs to share
36 //! data, so member variables are used. However, much of this data is irrelevant
37 //! after initialization is completed, so rather than doing it in
38 //! MachOImageSymbolTableReader, it’s handled by this class, which is a “friend”
39 //! of MachOImageSymbolTableReader.
40 class MachOImageSymbolTableReaderInitializer {
41  public:
MachOImageSymbolTableReaderInitializer(ProcessReaderMac * process_reader,const MachOImageSegmentReader * linkedit_segment,const std::string & module_info)42   MachOImageSymbolTableReaderInitializer(
43       ProcessReaderMac* process_reader,
44       const MachOImageSegmentReader* linkedit_segment,
45       const std::string& module_info)
46       : module_info_(module_info),
47         linkedit_range_(),
48         process_reader_(process_reader),
49         linkedit_segment_(linkedit_segment) {
50     linkedit_range_.SetRange(process_reader_->Is64Bit(),
51                              linkedit_segment->Address(),
52                              linkedit_segment->Size());
53     DCHECK(linkedit_range_.IsValid());
54   }
55 
~MachOImageSymbolTableReaderInitializer()56   ~MachOImageSymbolTableReaderInitializer() {}
57 
58   //! \brief Reads the symbol table from another process.
59   //!
60   //! \sa MachOImageSymbolTableReader::Initialize()
Initialize(const process_types::symtab_command * symtab_command,const process_types::dysymtab_command * dysymtab_command,MachOImageSymbolTableReader::SymbolInformationMap * external_defined_symbols)61   bool Initialize(const process_types::symtab_command* symtab_command,
62                   const process_types::dysymtab_command* dysymtab_command,
63                   MachOImageSymbolTableReader::SymbolInformationMap*
64                       external_defined_symbols) {
65     mach_vm_address_t symtab_address =
66         AddressForLinkEditComponent(symtab_command->symoff);
67     uint32_t symbol_count = symtab_command->nsyms;
68     size_t nlist_size = process_types::nlist::ExpectedSize(process_reader_);
69     mach_vm_size_t symtab_size = symbol_count * nlist_size;
70     if (!IsInLinkEditSegment(symtab_address, symtab_size, "symtab")) {
71       return false;
72     }
73 
74     // If a dysymtab is present, use it to filter the symtab for just the
75     // portion used for extdefsym. If no dysymtab is present, the entire symtab
76     // will need to be consulted.
77     uint32_t skip_count = 0;
78     if (dysymtab_command) {
79       if (dysymtab_command->iextdefsym >= symtab_command->nsyms ||
80           dysymtab_command->iextdefsym + dysymtab_command->nextdefsym >
81               symtab_command->nsyms) {
82         LOG(WARNING) << base::StringPrintf(
83                             "dysymtab extdefsym %u + %u > symtab nsyms %u",
84                             dysymtab_command->iextdefsym,
85                             dysymtab_command->nextdefsym,
86                             symtab_command->nsyms) << module_info_;
87         return false;
88       }
89 
90       skip_count = dysymtab_command->iextdefsym;
91       mach_vm_size_t skip_size = skip_count * nlist_size;
92       symtab_address += skip_size;
93       symtab_size -= skip_size;
94       symbol_count = dysymtab_command->nextdefsym;
95     }
96 
97     mach_vm_address_t strtab_address =
98         AddressForLinkEditComponent(symtab_command->stroff);
99     mach_vm_size_t strtab_size = symtab_command->strsize;
100     if (!IsInLinkEditSegment(strtab_address, strtab_size, "strtab")) {
101       return false;
102     }
103 
104     std::unique_ptr<process_types::nlist[]> symbols(
105         new process_types::nlist[symtab_command->nsyms]);
106     if (!process_types::nlist::ReadArrayInto(
107             process_reader_, symtab_address, symbol_count, &symbols[0])) {
108       LOG(WARNING) << "could not read symbol table" << module_info_;
109       return false;
110     }
111 
112     std::unique_ptr<ProcessMemoryMac::MappedMemory> string_table;
113     for (size_t symbol_index = 0; symbol_index < symbol_count; ++symbol_index) {
114       const process_types::nlist& symbol = symbols[symbol_index];
115       std::string symbol_info = base::StringPrintf(", symbol index %zu%s",
116                                                    skip_count + symbol_index,
117                                                    module_info_.c_str());
118       bool valid_symbol = true;
119       if ((symbol.n_type & N_STAB) == 0 && (symbol.n_type & N_PEXT) == 0 &&
120           (symbol.n_type & N_EXT)) {
121         uint8_t symbol_type = symbol.n_type & N_TYPE;
122         if (symbol_type == N_ABS || symbol_type == N_SECT) {
123           if (symbol.n_strx >= strtab_size) {
124             LOG(WARNING) << base::StringPrintf(
125                                 "string at 0x%x out of bounds (0x%llx)",
126                                 symbol.n_strx,
127                                 strtab_size) << symbol_info;
128             return false;
129           }
130 
131           if (!string_table) {
132             string_table = process_reader_->Memory()->ReadMapped(
133                 strtab_address, strtab_size);
134             if (!string_table) {
135               LOG(WARNING) << "could not read string table" << module_info_;
136               return false;
137             }
138           }
139 
140           std::string name;
141           if (!string_table->ReadCString(symbol.n_strx, &name)) {
142             LOG(WARNING) << "could not read string" << symbol_info;
143             return false;
144           }
145 
146           if (symbol_type == N_ABS && symbol.n_sect != NO_SECT) {
147             LOG(WARNING) << base::StringPrintf("N_ABS symbol %s in section %u",
148                                                name.c_str(),
149                                                symbol.n_sect) << symbol_info;
150             return false;
151           }
152 
153           if (symbol_type == N_SECT && symbol.n_sect == NO_SECT) {
154             LOG(WARNING) << base::StringPrintf(
155                                 "N_SECT symbol %s in section NO_SECT",
156                                 name.c_str()) << symbol_info;
157             return false;
158           }
159 
160           MachOImageSymbolTableReader::SymbolInformation this_symbol_info;
161           this_symbol_info.value = symbol.n_value;
162           this_symbol_info.section = symbol.n_sect;
163           if (!external_defined_symbols->insert(
164                   std::make_pair(name, this_symbol_info)).second) {
165             LOG(WARNING) << "duplicate symbol " << name << symbol_info;
166             return false;
167           }
168         } else {
169           // External indirect symbols may be found in the portion of the symbol
170           // table used for external symbols as opposed to indirect symbols when
171           // the indirect symbols are also external. These can be produced by
172           // Xcode 5.1 ld64-236.3/src/ld/LinkEditClassic.hpp
173           // ld::tool::SymbolTableAtom<>::addGlobal(). Indirect symbols are not
174           // currently supported by this symbol table reader, so ignore them
175           // without failing or logging a message when encountering them. See
176           // https://groups.google.com/a/chromium.org/d/topic/crashpad-dev/k7QkLwO71Zo
177           valid_symbol = symbol_type == N_INDR;
178         }
179       } else {
180         valid_symbol = false;
181       }
182       if (!valid_symbol && dysymtab_command) {
183         LOG(WARNING) << "non-external symbol with type " << symbol.n_type
184                      << " in extdefsym" << symbol_info;
185         return false;
186       }
187     }
188 
189     return true;
190   }
191 
192  private:
193   //! \brief Computes the address for data in the `__LINKEDIT` segment
194   //!     identified by its file offset in a Mach-O image.
195   //!
196   //! \param[in] fileoff The file offset relative to the beginning of an image’s
197   //!     `mach_header` or `mach_header_64` of the data in the `__LINKEDIT`
198   //!     segment.
199   //!
200   //! \return The address, in the remote process’ address space, of the
201   //!     requested data.
AddressForLinkEditComponent(uint32_t fileoff) const202   mach_vm_address_t AddressForLinkEditComponent(uint32_t fileoff) const {
203     return linkedit_range_.Base() + fileoff - linkedit_segment_->fileoff();
204   }
205 
206   //! \brief Determines whether an address range is located within the
207   //!     `__LINKEDIT` segment.
208   //!
209   //! \param[in] address The base address of the range to check.
210   //! \param[in] size The size of the range to check.
211   //! \param[in] tag A string that identifies the range being checked. This is
212   //!     used only for logging.
213   //!
214   //! \return `true` if the range identified by \a address + \a size lies
215   //!     entirely within the `__LINKEDIT` segment. `false` if that range is
216   //!     invalid, or if that range is not contained by the `__LINKEDIT`
217   //!     segment, with an appropriate message logged.
IsInLinkEditSegment(mach_vm_address_t address,mach_vm_size_t size,const char * tag) const218   bool IsInLinkEditSegment(mach_vm_address_t address,
219                            mach_vm_size_t size,
220                            const char* tag) const {
221     CheckedMachAddressRange subrange(process_reader_->Is64Bit(), address, size);
222     if (!subrange.IsValid()) {
223       LOG(WARNING) << base::StringPrintf("invalid %s range (0x%llx + 0x%llx)",
224                                          tag,
225                                          address,
226                                          size) << module_info_;
227       return false;
228     }
229 
230     if (!linkedit_range_.ContainsRange(subrange)) {
231       LOG(WARNING) << base::StringPrintf(
232                           "%s at 0x%llx + 0x%llx outside of " SEG_LINKEDIT
233                           " segment at 0x%llx + 0x%llx",
234                           tag,
235                           address,
236                           size,
237                           linkedit_range_.Base(),
238                           linkedit_range_.Size()) << module_info_;
239       return false;
240     }
241 
242     return true;
243   }
244 
245   std::string module_info_;
246   CheckedMachAddressRange linkedit_range_;
247   ProcessReaderMac* process_reader_;  // weak
248   const MachOImageSegmentReader* linkedit_segment_;  // weak
249 
250   DISALLOW_COPY_AND_ASSIGN(MachOImageSymbolTableReaderInitializer);
251 };
252 
253 }  // namespace internal
254 
MachOImageSymbolTableReader()255 MachOImageSymbolTableReader::MachOImageSymbolTableReader()
256     : external_defined_symbols_(), initialized_() {
257 }
258 
~MachOImageSymbolTableReader()259 MachOImageSymbolTableReader::~MachOImageSymbolTableReader() {
260 }
261 
Initialize(ProcessReaderMac * process_reader,const process_types::symtab_command * symtab_command,const process_types::dysymtab_command * dysymtab_command,const MachOImageSegmentReader * linkedit_segment,const std::string & module_info)262 bool MachOImageSymbolTableReader::Initialize(
263     ProcessReaderMac* process_reader,
264     const process_types::symtab_command* symtab_command,
265     const process_types::dysymtab_command* dysymtab_command,
266     const MachOImageSegmentReader* linkedit_segment,
267     const std::string& module_info) {
268   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
269 
270   internal::MachOImageSymbolTableReaderInitializer initializer(process_reader,
271                                                                linkedit_segment,
272                                                                module_info);
273   if (!initializer.Initialize(
274           symtab_command, dysymtab_command, &external_defined_symbols_)) {
275     return false;
276   }
277 
278   INITIALIZATION_STATE_SET_VALID(initialized_);
279   return true;
280 }
281 
282 const MachOImageSymbolTableReader::SymbolInformation*
LookUpExternalDefinedSymbol(const std::string & name) const283 MachOImageSymbolTableReader::LookUpExternalDefinedSymbol(
284     const std::string& name) const {
285   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
286 
287   const auto& iterator = external_defined_symbols_.find(name);
288   if (iterator == external_defined_symbols_.end()) {
289     return nullptr;
290   }
291   return &iterator->second;
292 }
293 
294 }  // namespace crashpad
295