]>
Commit | Line | Data |
---|---|---|
cf37c299 A |
1 | /* |
2 | * Copyright (c) 2016 Apple Inc. All rights reserved. | |
3 | */ | |
4 | ||
5 | #include "options.h" | |
6 | #include "dyld.h" | |
7 | #include "utils.h" | |
8 | #include "corefile.h" | |
9 | #include "vm.h" | |
10 | ||
11 | #include <mach-o/loader.h> | |
12 | #include <mach-o/fat.h> | |
13 | #include <mach-o/dyld_process_info.h> | |
14 | ||
15 | #include <mach/mach.h> | |
16 | #include <mach/task.h> | |
17 | #include <mach/mach_vm.h> | |
18 | #include <mach/shared_region.h> | |
19 | #include <sys/param.h> | |
20 | #include <stdio.h> | |
21 | #include <stdlib.h> | |
22 | #include <unistd.h> | |
23 | #include <time.h> | |
24 | #include <libgen.h> | |
25 | #include <sys/stat.h> | |
26 | ||
27 | /* | |
28 | * WARNING WARNING WARNING | |
29 | * | |
30 | * Do not trust any of the data from the target task. | |
31 | * | |
32 | * A broken program may have damaged it, or a malicious | |
33 | * program may have deliberately constructed something to | |
34 | * cause us harm. | |
35 | */ | |
36 | ||
37 | static const char warn_dyld_info[] = "dyld information is incomplete or damaged"; | |
38 | ||
39 | dyld_process_info | |
40 | get_task_dyld_info(const task_t task) | |
41 | { | |
42 | kern_return_t kret; | |
43 | dyld_process_info dpi = _dyld_process_info_create(task, 0, &kret); | |
44 | if (NULL == dpi) { | |
45 | err_mach(kret, NULL, "_dlyd_process_info_create"); | |
46 | } else { | |
47 | dyld_process_state_info stateInfo; | |
48 | ||
49 | _dyld_process_info_get_state(dpi, &stateInfo); | |
50 | switch (stateInfo.dyldState) { | |
51 | case dyld_process_state_not_started: | |
52 | warnx("%s: dyld state %d", warn_dyld_info, stateInfo.dyldState); | |
53 | _dyld_process_info_release(dpi); | |
54 | dpi = NULL; | |
55 | break; | |
56 | default: | |
57 | break; | |
58 | } | |
59 | } | |
60 | return dpi; | |
61 | } | |
62 | ||
63 | /* | |
64 | * Get the shared cache UUID iff it's in use and is the system one | |
65 | */ | |
66 | bool | |
67 | get_sc_uuid(dyld_process_info dpi, uuid_t uu) | |
68 | { | |
69 | dyld_process_cache_info cacheInfo; | |
70 | ||
71 | _dyld_process_info_get_cache(dpi, &cacheInfo); | |
72 | if (!cacheInfo.noCache && !cacheInfo.privateCache) { | |
73 | uuid_copy(uu, cacheInfo.cacheUUID); | |
74 | return true; | |
75 | } | |
76 | return false; | |
77 | } | |
78 | ||
79 | void | |
80 | free_task_dyld_info(dyld_process_info dpi) | |
81 | { | |
82 | _dyld_process_info_release(dpi); | |
83 | } | |
84 | ||
85 | /* | |
86 | * This routine collects both the Mach-O header and the commands | |
87 | * "below" it, assuming they're in contiguous memory. | |
88 | */ | |
89 | static native_mach_header_t * | |
90 | copy_dyld_image_mh(task_t task, mach_vm_address_t targetmh, const char *path) | |
91 | { | |
92 | vm_offset_t mhaddr = 0; | |
93 | mach_msg_type_number_t mhlen = sizeof (native_mach_header_t); | |
94 | ||
95 | for (int attempts = 0; attempts < 2; attempts++) { | |
96 | ||
97 | const kern_return_t ret = mach_vm_read(task, targetmh, mhlen, &mhaddr, &mhlen); | |
98 | if (KERN_SUCCESS != ret) { | |
99 | err_mach(ret, NULL, "mach_vm_read() at 0x%llx for image %s\n", targetmh, path); | |
100 | mhaddr = 0; | |
101 | break; | |
102 | } | |
103 | const native_mach_header_t *mh = (void *)mhaddr; | |
104 | if (mhlen < mh->sizeofcmds + sizeof (*mh)) { | |
105 | const mach_msg_type_number_t newmhlen = sizeof (*mh) + mh->sizeofcmds; | |
106 | mach_vm_deallocate(mach_task_self(), mhaddr, mhlen); | |
107 | mhlen = newmhlen; | |
108 | } else | |
109 | break; | |
110 | } | |
111 | ||
112 | native_mach_header_t *result = NULL; | |
113 | ||
114 | if (mhaddr) { | |
115 | if (NULL != (result = malloc(mhlen))) | |
116 | memcpy(result, (void *)mhaddr, mhlen); | |
117 | mach_vm_deallocate(mach_task_self(), mhaddr, mhlen); | |
118 | } | |
119 | return result; | |
120 | } | |
121 | ||
122 | /* | |
123 | * This table (list) describes libraries and the executable in the address space | |
124 | */ | |
125 | struct liblist { | |
126 | STAILQ_ENTRY(liblist) ll_linkage; | |
127 | unsigned long ll_namehash; | |
128 | struct libent ll_entry; | |
129 | }; | |
130 | static STAILQ_HEAD(, liblist) libhead = STAILQ_HEAD_INITIALIZER(libhead); | |
131 | ||
cf37c299 A |
132 | static const struct libent * |
133 | libent_lookup_bypathname_withhash(const char *nm, const unsigned long hash) | |
134 | { | |
135 | struct liblist *ll; | |
136 | STAILQ_FOREACH(ll, &libhead, ll_linkage) { | |
137 | if (hash != ll->ll_namehash) | |
138 | continue; | |
139 | struct libent *le = &ll->ll_entry; | |
140 | if (strcmp(nm, le->le_pathname) == 0) | |
141 | return le; | |
142 | } | |
143 | return NULL; | |
144 | } | |
145 | ||
146 | const struct libent * | |
147 | libent_lookup_byuuid(const uuid_t uuid) | |
148 | { | |
149 | struct liblist *ll; | |
150 | STAILQ_FOREACH(ll, &libhead, ll_linkage) { | |
151 | struct libent *le = &ll->ll_entry; | |
152 | if (uuid_compare(uuid, le->le_uuid) == 0) | |
153 | return le; | |
154 | } | |
155 | return NULL; | |
156 | } | |
157 | ||
158 | const struct libent * | |
159 | libent_lookup_first_bytype(uint32_t mhtype) | |
160 | { | |
161 | struct liblist *ll; | |
162 | STAILQ_FOREACH(ll, &libhead, ll_linkage) { | |
163 | struct libent *le = &ll->ll_entry; | |
164 | if (mhtype == le->le_mh->filetype) | |
165 | return le; | |
166 | } | |
167 | return NULL; | |
168 | } | |
169 | ||
170 | const struct libent * | |
887d5eed | 171 | libent_insert(const char *rawnm, const uuid_t uuid, uint64_t mhaddr, const native_mach_header_t *mh, const struct vm_range *vr, mach_vm_offset_t objoff) |
cf37c299 A |
172 | { |
173 | const struct libent *le = libent_lookup_byuuid(uuid); | |
174 | if (NULL != le) | |
175 | return le; // disallow multiple names for the same uuid | |
176 | ||
887d5eed A |
177 | char *nm = realpath(rawnm, NULL); |
178 | if (NULL == nm) | |
179 | nm = strdup(rawnm); | |
180 | const unsigned long nmhash = simple_namehash(nm); | |
cf37c299 | 181 | le = libent_lookup_bypathname_withhash(nm, nmhash); |
887d5eed A |
182 | if (NULL != le) { |
183 | free(nm); | |
cf37c299 | 184 | return le; |
887d5eed | 185 | } |
cf37c299 | 186 | |
887d5eed | 187 | if (OPTIONS_DEBUG(opt, 3)) { |
cf37c299 A |
188 | uuid_string_t uustr; |
189 | uuid_unparse_lower(uuid, uustr); | |
887d5eed A |
190 | printf("[adding <'%s', %s, 0x%llx, %p", nm, uustr, mhaddr, mh); |
191 | if (vr) | |
192 | printf(" (%llx-%llx)", V_ADDR(vr), V_ENDADDR(vr)); | |
193 | printf(">]\n"); | |
cf37c299 A |
194 | } |
195 | struct liblist *ll = malloc(sizeof (*ll)); | |
196 | ll->ll_namehash = nmhash; | |
887d5eed | 197 | ll->ll_entry.le_pathname = nm; |
cf37c299 A |
198 | ll->ll_entry.le_filename = strrchr(ll->ll_entry.le_pathname, '/'); |
199 | if (NULL == ll->ll_entry.le_filename) | |
200 | ll->ll_entry.le_filename = ll->ll_entry.le_pathname; | |
201 | else | |
202 | ll->ll_entry.le_filename++; | |
203 | uuid_copy(ll->ll_entry.le_uuid, uuid); | |
204 | ll->ll_entry.le_mhaddr = mhaddr; | |
205 | ll->ll_entry.le_mh = mh; | |
887d5eed A |
206 | if (vr) |
207 | ll->ll_entry.le_vr = *vr; | |
208 | else { | |
209 | V_SETADDR(&ll->ll_entry.le_vr, MACH_VM_MAX_ADDRESS); | |
210 | V_SETSIZE(&ll->ll_entry.le_vr, 0); | |
211 | } | |
212 | ll->ll_entry.le_objoff = objoff; | |
cf37c299 A |
213 | STAILQ_INSERT_HEAD(&libhead, ll, ll_linkage); |
214 | ||
215 | return &ll->ll_entry; | |
216 | } | |
217 | ||
218 | bool | |
219 | libent_build_nametable(task_t task, dyld_process_info dpi) | |
220 | { | |
221 | __block bool valid = true; | |
222 | ||
887d5eed | 223 | _dyld_process_info_for_each_image(dpi, ^(uint64_t mhaddr, const uuid_t uuid, const char *path) { |
cf37c299 A |
224 | if (valid) { |
225 | native_mach_header_t *mh = copy_dyld_image_mh(task, mhaddr, path); | |
226 | if (mh) { | |
227 | /* | |
228 | * Validate the rest of the mach information in the header before attempting optimizations | |
229 | */ | |
230 | const size_t mhlen = sizeof (*mh) + mh->sizeofcmds; | |
231 | const struct load_command *lc = (const void *)(mh + 1); | |
887d5eed A |
232 | struct vm_range vr = { |
233 | .addr = MACH_VM_MAX_ADDRESS, | |
234 | .size = 0 | |
235 | }; | |
236 | mach_vm_offset_t objoff = MACH_VM_MAX_ADDRESS; | |
cf37c299 A |
237 | |
238 | for (unsigned n = 0; n < mh->ncmds; n++) { | |
239 | if (((uintptr_t)lc & 0x3) != 0 || | |
240 | (uintptr_t)lc < (uintptr_t)mh || (uintptr_t)lc > (uintptr_t)mh + mhlen) { | |
241 | warnx("%s, %d", warn_dyld_info, __LINE__); | |
242 | valid = false; | |
243 | break; | |
244 | } | |
887d5eed A |
245 | switch (lc->cmd) { |
246 | case NATIVE_LC_SEGMENT: { | |
247 | const native_segment_command_t *sc = (const void *)lc; | |
248 | ||
249 | char scsegname[17]; | |
250 | strlcpy(scsegname, sc->segname, sizeof (scsegname)); | |
251 | ||
252 | if (0 == sc->vmaddr && | |
253 | strcmp(scsegname, SEG_PAGEZERO) == 0) | |
254 | break; | |
255 | ||
256 | /* | |
257 | * -Depends- on finding a __TEXT segment first | |
258 | * which implicitly maps the mach header too | |
259 | */ | |
260 | ||
261 | if (MACH_VM_MAX_ADDRESS == objoff) { | |
262 | if (strcmp(scsegname, SEG_TEXT) == 0) { | |
263 | objoff = mhaddr - sc->vmaddr; | |
264 | V_SETADDR(&vr, mhaddr); | |
265 | V_SETSIZE(&vr, sc->vmsize); | |
266 | } else { | |
267 | printf("%s: expected %s segment, found %s\n", path, SEG_TEXT, scsegname); | |
268 | valid = false; | |
269 | break; | |
270 | } | |
271 | } | |
272 | ||
273 | mach_vm_offset_t lo = sc->vmaddr + objoff; | |
274 | mach_vm_offset_t hi = lo + sc->vmsize; | |
275 | ||
276 | if (V_SIZE(&vr)) { | |
277 | if (lo < V_ADDR(&vr)) { | |
278 | mach_vm_offset_t newsize = V_SIZE(&vr) + (V_ADDR(&vr) - lo); | |
279 | V_SETSIZE(&vr, newsize); | |
280 | V_SETADDR(&vr, lo); | |
281 | } | |
282 | if (hi > V_ENDADDR(&vr)) { | |
283 | V_SETSIZE(&vr, (hi - V_ADDR(&vr))); | |
284 | } | |
285 | } else { | |
286 | V_SETADDR(&vr, lo); | |
287 | V_SETSIZE(&vr, hi - lo); | |
288 | } | |
289 | assert(lo >= V_ADDR(&vr) && hi <= V_ENDADDR(&vr)); | |
290 | } break; | |
291 | #if defined(RDAR_28040018) | |
292 | case LC_ID_DYLINKER: | |
293 | if (MH_DYLINKER == mh->filetype) { | |
294 | /* workaround: the API doesn't always return the right name */ | |
295 | const struct dylinker_command *dc = (const void *)lc; | |
296 | path = dc->name.offset + (const char *)dc; | |
297 | } | |
298 | break; | |
299 | #endif | |
300 | default: | |
301 | break; | |
302 | } | |
303 | if (NULL == (lc = next_lc(lc))) | |
cf37c299 A |
304 | break; |
305 | } | |
887d5eed A |
306 | if (valid) |
307 | (void) libent_insert(path, uuid, mhaddr, mh, &vr, objoff); | |
cf37c299 A |
308 | } |
309 | } | |
310 | }); | |
887d5eed | 311 | if (OPTIONS_DEBUG(opt, 3)) |
cf37c299 A |
312 | printf("nametable %sconstructed\n", valid ? "" : "NOT "); |
313 | return valid; | |
314 | } |