1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <elf-search.h>
6 
7 #include <assert.h>
8 #include <elf.h>
9 #include <inttypes.h>
10 #include <stdbool.h>
11 #include <stddef.h>
12 #include <stdint.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <zircon/status.h>
17 #include <zircon/syscalls.h>
18 #include <zircon/syscalls/object.h>
19 
20 namespace {
21 
22 // This is a reasonable upper limit on the number of program headers that are
23 // expected. 7 or 8 is more typical.
24 constexpr size_t kMaxProgramHeaders = 16;
25 // kWindowSize is a tuning parameter. It specifies how much memory should be
26 // read in by ProcessMemReader when a new read is needed. The goal is to
27 // optimize the trade-off between making too many system calls and reading in
28 // too much memory. The larger kWindowSize is the fewer system calls are made
29 // but the more bytes are copied over that don't need to be. The smaller it is
30 // the more system calls need to be made but the fewer superfluous bytes are
31 // copied.
32 // TODO(jakehehrlich): Tune kWindowSize rather than just guessing.
33 constexpr size_t kWindowSize = 0x400;
34 // This is an upper bound on the number of bytes that can be used in a build ID.
35 // md5 and sha1 are the most common hashes used for build ids and they use 20
36 // and 16 bytes respectively. This makes 32 a generous upper bound.
37 constexpr size_t kMaxBuildIDSize = 32;
38 
IsPossibleLoadedEhdr(const Elf64_Ehdr & ehdr)39 bool IsPossibleLoadedEhdr(const Elf64_Ehdr& ehdr) {
40     // Do some basic sanity checks including checking the ELF identifier
41     return ehdr.e_ident[EI_MAG0] == ELFMAG0 &&
42            ehdr.e_ident[EI_MAG1] == ELFMAG1 &&
43            ehdr.e_ident[EI_MAG2] == ELFMAG2 &&
44            ehdr.e_ident[EI_MAG3] == ELFMAG3 &&
45            ehdr.e_ident[EI_CLASS] == ELFCLASS64 &&
46            ehdr.e_ident[EI_DATA] == ELFDATA2LSB &&
47            ehdr.e_ident[EI_VERSION] == EV_CURRENT &&
48            ehdr.e_type == ET_DYN &&
49            ehdr.e_machine == kNativeElfMachine &&
50            ehdr.e_version == EV_CURRENT &&
51            ehdr.e_ehsize == sizeof(Elf64_Ehdr) &&
52            ehdr.e_phentsize == sizeof(Elf64_Phdr) &&
53            ehdr.e_phnum > 0 &&
54            (ehdr.e_phoff % alignof(Elf64_Phdr) == 0);
55 }
56 
57 // TODO(jakehehrlich): Switch uses of uint8_t to std::byte where appropriate.
58 
59 class ProcessMemReader {
60 public:
ProcessMemReader(const zx::process & proc)61     ProcessMemReader(const zx::process& proc)
62         : process_(proc) {}
63 
64     // TODO (jakehehrlich): Make this interface zero-copy (by returning a
65     // a pointer rather than copying for instance). It's important that
66     // the lifetime of the underlying storage is correctly managed.
67     template <typename T>
Read(uintptr_t vaddr,T * x)68     [[nodiscard]] zx_status_t Read(uintptr_t vaddr, T* x) {
69         return ReadBytes(vaddr, reinterpret_cast<uint8_t*>(x), sizeof(T));
70     }
71     template <typename T>
ReadArray(uintptr_t vaddr,fbl::Array<T> * arr)72     [[nodiscard]] zx_status_t ReadArray(uintptr_t vaddr, fbl::Array<T>* arr) {
73         return ReadBytes(vaddr, arr->get(), arr->size());
74     }
75     template <typename T>
ReadArray(uintptr_t vaddr,T * arr,size_t sz)76     [[nodiscard]] zx_status_t ReadArray(uintptr_t vaddr, T* arr, size_t sz) {
77         return ReadBytes(vaddr, reinterpret_cast<uint8_t*>(arr), sz * sizeof(T));
78     }
79 
80 private:
81     const zx::process& process_;
82     uint8_t window_[kWindowSize];
83     uintptr_t window_start_ = 0;
84     size_t window_size_ = 0;
85 
ReadBytes(uintptr_t vaddr,uint8_t * mem,size_t size)86     zx_status_t ReadBytes(uintptr_t vaddr, uint8_t* mem, size_t size) {
87         if (vaddr >= window_start_ && vaddr - window_start_ < window_size_) {
88             size_t from_window_size = fbl::min(size, window_size_ - (vaddr - window_start_));
89             memcpy(mem, window_ + (vaddr - window_start_), from_window_size);
90             vaddr += from_window_size;
91             mem += from_window_size;
92             size -= from_window_size;
93         }
94         while (size > 0) {
95             // TODO(jakehehrlich): Only read into window on the last iteration of this loop.
96             size_t actual;
97             zx_status_t status = process_.read_memory(vaddr, window_, kWindowSize, &actual);
98             if (status != ZX_OK) {
99                 return status;
100             }
101             window_start_ = vaddr;
102             window_size_ = actual;
103             size_t bytes_read = fbl::min(actual, size);
104             memcpy(mem, window_, bytes_read);
105             vaddr += bytes_read;
106             mem += bytes_read;
107             size -= bytes_read;
108         }
109         return ZX_OK;
110     }
111 };
112 
GetBuildID(ProcessMemReader * reader,uintptr_t base,const Elf64_Phdr & notes,uint8_t * buildID,size_t * buildIDSize)113 [[nodiscard]] zx_status_t GetBuildID(ProcessMemReader* reader, uintptr_t base, const Elf64_Phdr& notes, uint8_t* buildID, size_t* buildIDSize) {
114     auto NoteAlign = [](uint32_t x) { return (x + 3) & -4; };
115     // TODO(jakehehrlich): Sanity check here that notes.p_vaddr falls in the [p_vaddr,p_vaddr+p_filesz) range of some RO PT_LOAD.
116     // TODO(jakehehrlich): Check that base is actually the bias and do something to alert the user to base not being the bias.
117     uintptr_t vaddr = base + notes.p_vaddr;
118     uintptr_t end = vaddr + notes.p_filesz;
119     // If the virtual address we found is not aligned or the ending overflowed return early.
120     if ((vaddr & 3) || end < vaddr) {
121         return ZX_ERR_NOT_FOUND;
122     }
123     while (end - vaddr > sizeof(Elf64_Nhdr)) {
124         Elf64_Nhdr nhdr;
125         zx_status_t status = reader->Read(vaddr, &nhdr);
126         if (status != ZX_OK) {
127             return status;
128         }
129         vaddr += sizeof(Elf64_Nhdr);
130         if (end - vaddr < NoteAlign(nhdr.n_namesz)) {
131             break;
132         }
133         uintptr_t nameAddr = vaddr;
134         vaddr += NoteAlign(nhdr.n_namesz);
135         if (end - vaddr < NoteAlign(nhdr.n_descsz)) {
136             break;
137         }
138         uintptr_t descAddr = vaddr;
139         vaddr += NoteAlign(nhdr.n_descsz);
140         // TODO(jakehehrlich): If descsz is larger than kMaxBuildIDSize but
141         // the type and name indicate that this note entry is a build ID we
142         // should log a warning to the user.
143         if (nhdr.n_type == NT_GNU_BUILD_ID && nhdr.n_namesz == sizeof(ELF_NOTE_GNU) && nhdr.n_descsz <= kMaxBuildIDSize) {
144             char name[sizeof(ELF_NOTE_GNU)];
145             status = reader->ReadArray(nameAddr, name, nhdr.n_namesz);
146             if (status != ZX_OK) {
147                 return status;
148             }
149             if (memcmp(name, ELF_NOTE_GNU, nhdr.n_namesz) == 0) {
150                 status = reader->ReadArray(descAddr, buildID, nhdr.n_descsz);
151                 if (status != ZX_OK) {
152                     return status;
153                 }
154                 *buildIDSize = nhdr.n_descsz;
155                 return ZX_OK;
156             }
157         }
158     }
159     return ZX_ERR_NOT_FOUND;
160 }
161 
162 } // anonymous namespace
163 
ForEachModule(const zx::process & process,ModuleAction action)164 zx_status_t ForEachModule(const zx::process& process, ModuleAction action) {
165     ProcessMemReader reader(process);
166 
167     // Read in the process maps.
168     size_t actual, avail;
169     zx_status_t status = process.get_info(ZX_INFO_PROCESS_MAPS, nullptr, 0, &actual, &avail);
170     if (status != ZX_OK) {
171         return status;
172     }
173     fbl::AllocChecker ac;
174     fbl::Array<zx_info_maps> maps(new (&ac) zx_info_maps[avail], avail);
175     if (!ac.check()) {
176         return ZX_ERR_NO_MEMORY;
177     }
178     status = process.get_info(ZX_INFO_PROCESS_MAPS, maps.get(), avail * sizeof(zx_info_maps), &actual, &avail);
179     if (status != ZX_OK) {
180         return status;
181     }
182     // TODO(jakehehrlich): Check permissions of program headers to make sure they agree with mappings.
183     // 'maps' should be sorted in ascending order of base address so we should be able to use that to
184     // quickly find the mapping associated with any given PT_LOAD.
185 
186     for (const auto& map : maps) {
187         // Skip any writable maps since the RODATA segment containing the
188         // headers will not be writable.
189         if (map.type != ZX_INFO_MAPS_TYPE_MAPPING) {
190             continue;
191         }
192         if ((map.u.mapping.mmu_flags & ZX_VM_PERM_WRITE) != 0) {
193             continue;
194         }
195         // Skip any mapping that doesn't start at the beginning of a VMO.
196         // We assume that the VMO represents the ELF file. ELF headers
197         // always start at the beginning of the file so if our assumption
198         // holds then we can't be looking at the start of an ELF header if
199         // the offset into the VMO isn't also zero.
200         if (map.u.mapping.vmo_offset != 0) {
201             continue;
202         }
203 
204         // Read in what might be an ELF header.
205         Elf64_Ehdr ehdr;
206         status = reader.Read(map.base, &ehdr);
207         if (status != ZX_OK) {
208             continue;
209         }
210         // Do some basic checks to see if this could ever be an ELF file.
211         if (!IsPossibleLoadedEhdr(ehdr)) {
212             continue;
213         }
214 
215         // We only support ELF files with <= 16 program headers.
216         // TODO(jakehehrlich): Log this because with the exception of core dumps
217         // almost nothing should get here *and* have such a large number of phdrs.
218         // This might indicate a larger issue.
219         if (ehdr.e_phnum > kMaxProgramHeaders) {
220             continue;
221         }
222         Elf64_Phdr phdrs_buf[kMaxProgramHeaders];
223         auto phdrs = MakeArrayRef(phdrs_buf, ehdr.e_phnum);
224         status = reader.ReadArray(map.base + ehdr.e_phoff, phdrs_buf, ehdr.e_phnum);
225         if (status != ZX_OK) {
226             continue;
227         }
228 
229         // Loop though program headers looking for a build ID.
230         uint8_t build_id_buf[kMaxBuildIDSize];
231         ArrayRef<uint8_t> build_id;
232         for (const auto& phdr : phdrs) {
233             if (phdr.p_type == PT_NOTE) {
234                 size_t size;
235                 status = GetBuildID(&reader, map.base, phdr, build_id_buf, &size);
236                 if (status == ZX_OK && size != 0) {
237                     build_id = MakeArrayRef(build_id_buf, size);
238                     break;
239                 }
240             }
241         }
242         // We're not considering otherwise valid files with no build id here.
243         // TODO(jakehehrlich): Consider reporting loaded modules with no build ID.
244         if (build_id.empty()) {
245             continue;
246         }
247 
248         // TODO(jakehehrlich): Synthesize a better name via PT_DYNAMIC->DT_SONAME.
249         char name[ZX_MAX_NAME_LEN];
250         if (map.name[0] == '\0') {
251             snprintf(name, sizeof(name), "<VMO#%" PRIu64 ">", map.u.mapping.vmo_koid);
252         } else {
253             snprintf(name, sizeof(name), "<VMO#%" PRIu64 "=%s>", map.u.mapping.vmo_koid, map.name);
254         }
255 
256         // All checks have passed so we can give the user a module.
257         action(ModuleInfo{
258             .name = fbl::StringPiece(name),
259             .vaddr = map.base,
260             .build_id = build_id,
261             .ehdr = ehdr,
262             .phdrs = phdrs,
263         });
264     }
265 
266     return ZX_OK;
267 }
268