Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 2024 Keve Müller <kevemueller@users.github.com>
3 : : *
4 : : * SPDX-License-Identifier: BSD-2-Clause
5 : : */
6 : :
7 : : #include <errno.h>
8 : :
9 : : #include "private/binfmt.h"
10 : : #include "private/binfmt_macho.h"
11 : : #include "private/pkg.h"
12 : : #include "private/event.h"
13 : : #include "private/pkg_abi.h"
14 : :
15 : : /**
16 : : * Routines to support pkg_abi.c functions when dealing with Mach-O files.
17 : : * Supports getting struct pkg_abi from the binary's load commands.
18 : : * Supports getting shared libary information (needed, provided & loader).
19 : : * Picks right binary in Universal binary based on ABI.
20 : : */
21 : :
22 : : static enum pkg_arch
23 : 45 : cputype_to_pkg_arch(const cpu_type_subtype_t cpu)
24 : : {
25 [ - + - + ]: 45 : switch (cpu.type) {
26 : : case CPU_TYPE_ARM:
27 [ - + ]: 15 : if (cpu.type_is64_32) {
28 : 0 : return (PKG_ARCH_UNKNOWN); /* aarch64-x32 */
29 [ + - ]: 15 : } else if (cpu.type_is64) {
30 : 15 : return (PKG_ARCH_AARCH64);
31 : : } else {
32 [ # # # # : 0 : switch (cpu.subtype_arm) {
# # # # #
# # # ]
33 : : case CPU_SUBTYPE_ARM_V7:
34 : : case CPU_SUBTYPE_ARM_V7S:
35 : : case CPU_SUBTYPE_ARM_V7K:
36 : : case CPU_SUBTYPE_ARM_V7M:
37 : : case CPU_SUBTYPE_ARM_V7EM:
38 : 0 : return (PKG_ARCH_ARMV7);
39 : : case CPU_SUBTYPE_ARM_V6:
40 : : case CPU_SUBTYPE_ARM_V6M:
41 : 0 : return (PKG_ARCH_ARMV6);
42 : : case CPU_SUBTYPE_ARM_XSCALE:
43 : : case CPU_SUBTYPE_ARM_V5:
44 : : case CPU_SUBTYPE_ARM_V4T:
45 : 0 : case CPU_SUBTYPE_ARM_ALL:
46 : : default:
47 : 0 : return (PKG_ARCH_UNKNOWN);
48 : : }
49 : : }
50 : : case CPU_TYPE_POWERPC:
51 [ # # ]: 0 : if (cpu.type_is64_32) {
52 : 0 : return (PKG_ARCH_UNKNOWN); /* powerpc64-x32 */
53 [ # # ]: 0 : } else if (cpu.type_is64) {
54 : 0 : return (PKG_ARCH_POWERPC64);
55 : : } else {
56 : 0 : return (PKG_ARCH_POWERPC);
57 : : }
58 : : case CPU_TYPE_X86:
59 [ - + ]: 30 : if (cpu.type_is64_32) {
60 : 0 : return (PKG_ARCH_UNKNOWN); /* amd64-x32 */
61 [ + - ]: 30 : } else if (cpu.type_is64) {
62 : 30 : return (PKG_ARCH_AMD64);
63 : : } else {
64 : 0 : return (PKG_ARCH_I386);
65 : : }
66 : : default:
67 : 0 : return (PKG_ARCH_UNKNOWN);
68 : : }
69 : 45 : }
70 : :
71 : : static cpu_type_subtype_t
72 : 25 : pkg_arch_to_cputype(enum pkg_arch arch) {
73 : 25 : cpu_type_subtype_t cpu = { 0 };
74 : :
75 [ - + + - : 25 : switch (arch) {
- + - - -
- - - ]
76 : : case PKG_ARCH_AARCH64:
77 : 9 : cpu.type = CPU_TYPE_ARM;
78 : 9 : cpu.type_is64 = true;
79 : 9 : break;
80 : : case PKG_ARCH_AMD64:
81 : 12 : cpu.type = CPU_TYPE_X86;
82 : 12 : cpu.type_is64 = true;
83 : 12 : cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
84 : 12 : break;
85 : : case PKG_ARCH_ARMV6:
86 : 0 : cpu.type = CPU_TYPE_ARM;
87 : 0 : cpu.subtype_arm = CPU_SUBTYPE_ARM_V6;
88 : 0 : break;
89 : : case PKG_ARCH_ARMV7:
90 : 0 : cpu.type = CPU_TYPE_ARM;
91 : 0 : cpu.subtype_arm = CPU_SUBTYPE_ARM_V7;
92 : 0 : break;
93 : : case PKG_ARCH_I386:
94 : 4 : cpu.type = CPU_TYPE_X86;
95 : 4 : cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
96 : 4 : break;
97 : : case PKG_ARCH_POWERPC:
98 : 0 : cpu.type = CPU_TYPE_POWERPC;
99 : 0 : cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
100 : 0 : break;
101 : : case PKG_ARCH_POWERPC64:
102 : 0 : cpu.type = CPU_TYPE_POWERPC;
103 : 0 : cpu.type_is64 = true;
104 : 0 : cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
105 : 0 : break;
106 : : case PKG_ARCH_POWERPC64LE:
107 : : case PKG_ARCH_RISCV32:
108 : : case PKG_ARCH_RISCV64:
109 : : case PKG_ARCH_UNKNOWN:
110 : 0 : cpu.type = CPU_TYPE_ANY;
111 : 0 : break;
112 : : }
113 : :
114 : 25 : return cpu;
115 : : }
116 : :
117 : :
118 : : /**
119 : : * Using the passed mf descriptor, match the best entry using the provided hint.
120 : : * No hint or no architecture in hint -> first entry. Debug1 warning if this is not precise match (there were multiple to choose from)
121 : : * Hint -> always match, even if single architecture in file. Notice if match fails and return null.
122 : : */
123 : : static const fat_arch_t *
124 : 44 : match_entry(macho_file_t *mf, enum pkg_arch arch_hint)
125 : : {
126 : 44 : const fat_arch_t *p = mf->arch;
127 [ + + ]: 44 : if (arch_hint != PKG_ARCH_UNKNOWN) {
128 : 25 : const cpu_type_subtype_t cpu_hint = pkg_arch_to_cputype(arch_hint);
129 : 25 : const fat_arch_t *p_end = p + mf->narch;
130 [ + + ]: 39 : while (p < p_end) {
131 : : // do not match cpu_hint.type == CPU_TYPE_ANY which is used if the
132 : : // arch_hint was not recognized
133 [ + + + + ]: 35 : if (p->cpu.type == cpu_hint.type &&
134 : 23 : p->cpu.type_is64 == cpu_hint.type_is64) {
135 [ - + - + ]: 21 : switch (cpu_hint.type) {
136 : : case CPU_TYPE_ARM:
137 [ # # ]: 9 : if (p->cpu.subtype_arm ==
138 [ - + ]: 9 : CPU_SUBTYPE_ARM_ALL ||
139 : 0 : cpu_hint.subtype_arm ==
140 [ # # ]: 0 : CPU_SUBTYPE_ARM_ALL ||
141 : 0 : p->cpu.subtype_arm ==
142 : 0 : cpu_hint.subtype_arm) {
143 : 9 : return p;
144 : : }
145 : 0 : break;
146 : : case CPU_TYPE_POWERPC:
147 [ # # ]: 0 : if (p->cpu.subtype_ppc ==
148 [ # # ]: 0 : CPU_SUBTYPE_POWERPC_ALL ||
149 : 0 : cpu_hint.subtype_ppc ==
150 [ # # ]: 0 : CPU_SUBTYPE_POWERPC_ALL ||
151 : 0 : p->cpu.subtype_ppc ==
152 : 0 : cpu_hint.subtype_ppc) {
153 : 0 : return p;
154 : : }
155 : 0 : break;
156 : : case CPU_TYPE_X86:
157 [ # # ]: 12 : if (p->cpu.subtype_x86 ==
158 [ - + ]: 12 : CPU_SUBTYPE_X86_ALL ||
159 : 0 : cpu_hint.subtype_x86 ==
160 [ # # ]: 0 : CPU_SUBTYPE_X86_ALL ||
161 : 0 : p->cpu.subtype_x86 ==
162 : 0 : cpu_hint.subtype_x86) {
163 : 12 : return p;
164 : : }
165 : 0 : break;
166 : : default:
167 : 0 : break;
168 : : }
169 : 0 : }
170 : 14 : pkg_debug(1, "Looking for %s, did not match %s",
171 : 14 : pkg_arch_to_string(PKG_OS_DARWIN, arch_hint),
172 : 14 : pkg_arch_to_string(PKG_OS_DARWIN, cputype_to_pkg_arch(p->cpu)));
173 : 14 : p++;
174 : : }
175 : 4 : pkg_emit_notice("Scanned %d entr%s, found none matching selector %s",
176 : 4 : mf->narch, mf->narch > 1 ? "ies" : "y",
177 : 4 : pkg_arch_to_string(PKG_OS_DARWIN, arch_hint));
178 : 4 : return 0;
179 [ + + ]: 19 : } else if (mf->narch > 1 ) {
180 : 8 : pkg_debug(1,"Found %"PRIu32" entries in universal binary, picking first",
181 : 8 : mf->narch);
182 : 8 : }
183 : 19 : return p;
184 : 44 : }
185 : :
186 : : /**
187 : : * With a not-null, potentially pre-populated os_info structure, fill
188 : : * all members of os_info except altabi with values obtained by parsing the Mach-O
189 : : * file passed with file descriptor.
190 : : *
191 : : * The arch_hint is used to determine the fat entry to be parsed in a universal
192 : : * binary. If arch_hint is PKG_ARCH_UNKNOWN, the first entry is used.
193 : : *
194 : : * Returns EPKG_OK if all went fine, EPKG_FATAL if anything went wrong.
195 : : * Seeks the file descriptor to an arbitrary position.
196 : : */
197 : : int
198 : 35 : pkg_macho_abi_from_fd(int fd, struct pkg_abi *abi, enum pkg_arch arch_hint)
199 : : {
200 : 35 : *abi = (struct pkg_abi){0};
201 : :
202 : : ssize_t x;
203 : 35 : pkg_error_t ret = EPKG_FATAL;
204 : :
205 : 35 : macho_file_t *mf = 0;
206 : 35 : build_version_t *bv = 0;
207 : :
208 [ + - ]: 35 : if ((x = read_macho_file(fd, &mf)) < 0) {
209 : 0 : goto cleanup;
210 : : }
211 : :
212 : 35 : const fat_arch_t *p = match_entry(mf, arch_hint);
213 : :
214 [ + + ]: 35 : if (!p) {
215 : 4 : goto cleanup;
216 : : }
217 : :
218 [ - + ]: 31 : if (-1 == (x = lseek(fd, p->offset, SEEK_SET))) {
219 : 0 : goto cleanup;
220 : : }
221 : 31 : size_t n = 0;
222 : : macho_header_t mh;
223 [ + - ]: 31 : if ((x = read_macho_header(fd, &mh)) < 0) {
224 : 0 : goto cleanup;
225 : : }
226 : 31 : const bool swap = mh.swap;
227 : 31 : n = 0;
228 [ + + ]: 488 : for (uint32_t ui = mh.ncmds; ui-- > 0;) {
229 : 457 : size_t n0 = n;
230 : : uint32_t loadcmdtype;
231 : : uint32_t loadcmdsize;
232 [ - + ]: 457 : READ(u32, loadcmdtype);
233 [ - + ]: 457 : READ(u32, loadcmdsize);
234 : 457 : enum MachOLoadCommand loadcmd = loadcmdtype & ~LC_REQ_DYLD;
235 [ - + - + : 457 : switch (loadcmd) {
+ - ]
236 : : case LC_BUILD_VERSION:
237 [ + - ]: 14 : if (bv) { // overwrite previous LC_VERSION_MIN_X
238 : : // values
239 : 0 : free(bv);
240 : 0 : bv = 0;
241 : 0 : }
242 [ + - ]: 14 : READ(build_version, bv);
243 : 31 : break;
244 : : case LC_VERSION_MIN_IPHONEOS:
245 : : case LC_VERSION_MIN_MACOSX:
246 : : case LC_VERSION_MIN_TVOS:
247 : : case LC_VERSION_MIN_WATCHOS:
248 [ - + ]: 443 : if (!bv) {
249 [ - + - + ]: 34 : if ((x = read_min_version(fd, swap, loadcmd,
250 : 17 : &bv)) < 0) {
251 : 0 : goto cleanup;
252 : : }
253 : 17 : n += x;
254 : 17 : break;
255 : : }
256 : : // have seen the more precise
257 : : // LC_BUILD_VERSION already
258 : : // fall through and disregard this
259 : : default:
260 : 426 : break;
261 : : }
262 : 457 : const uint32_t fill = loadcmdsize - (n - n0);
263 [ + + + - ]: 457 : if (fill && -1 == (x = lseek(fd, fill, SEEK_CUR))) {
264 : 0 : goto cleanup;
265 : : }
266 : 457 : n += fill;
267 [ - + ]: 457 : if (n > mh.sizeofcmds) {
268 : : // we passed the frame boundary of the load commands
269 : 0 : pkg_emit_error("Mach-O structure misread.");
270 : 0 : errno = EINVAL;
271 : 0 : goto cleanup;
272 : : }
273 : : }
274 : :
275 [ + - ]: 62 : if (bv) {
276 : : macho_version_t darwin;
277 : 31 : map_platform_to_darwin(&darwin, bv->platform, bv->minos);
278 : :
279 : 31 : abi->os = PKG_OS_DARWIN;
280 : :
281 : 31 : abi->major = darwin.major;
282 : 31 : abi->minor = darwin.minor;
283 : 31 : abi->patch = darwin.patch;
284 : :
285 : 31 : abi->arch = cputype_to_pkg_arch(mh.cpu);
286 : :
287 [ + - ]: 31 : if (abi->arch == PKG_ARCH_UNKNOWN) {
288 : 0 : ret = EPKG_FATAL;
289 : 0 : } else {
290 : 31 : ret = EPKG_OK;
291 : : }
292 : 31 : } else {
293 : 0 : pkg_emit_notice("No OS version information found in binary.");
294 : 0 : ret = EPKG_WARN;
295 : : }
296 : :
297 : : cleanup:
298 : 35 : free(bv);
299 : 35 : free(mf);
300 : 35 : return ret;
301 : 35 : }
302 : :
303 : : static int
304 : 9 : analyse_macho(int fd, struct pkg *pkg)
305 : : {
306 : : ssize_t x;
307 : 9 : pkg_error_t ret = EPKG_END;
308 : :
309 : 9 : macho_file_t *mf = 0;
310 : :
311 [ + - ]: 9 : if ((x = read_macho_file(fd, &mf)) < 0) {
312 : 0 : goto cleanup;
313 : : }
314 : :
315 : 9 : const fat_arch_t *p = match_entry(mf, ctx.abi.arch);
316 : :
317 [ + - ]: 9 : if (!p) {
318 : 0 : goto cleanup;
319 : : }
320 : :
321 [ - + ]: 9 : if (-1 == (x = lseek(fd, p->offset, SEEK_SET))) {
322 : 0 : goto cleanup;
323 : : }
324 : 9 : size_t n = 0;
325 : : macho_header_t mh;
326 [ + - ]: 9 : if ((x = read_macho_header(fd, &mh)) < 0) {
327 : 0 : goto cleanup;
328 : : }
329 : 9 : const bool swap = mh.swap;
330 : 9 : n = 0;
331 [ + + ]: 140 : for (uint32_t ui = mh.ncmds; ui-- > 0;) {
332 : 131 : size_t n0 = n;
333 : : uint32_t loadcmdtype;
334 : : uint32_t loadcmdsize;
335 [ - + ]: 131 : READ(u32, loadcmdtype);
336 [ - + ]: 131 : READ(u32, loadcmdsize);
337 : 131 : enum MachOLoadCommand loadcmd = loadcmdtype & ~LC_REQ_DYLD;
338 [ - + + + : 131 : switch (loadcmd) {
- - - +
- ]
339 : : case LC_RPATH:
340 : : case LC_LOAD_DYLINKER:;
341 : 6 : char *dylinker = 0;
342 [ + - + - ]: 12 : if ((x = read_path(fd, swap, loadcmdsize,
343 : 6 : &dylinker)) < 0) {
344 : 0 : goto cleanup;
345 : : }
346 : 6 : n += x;
347 : 6 : pkg_debug(3, "load_dylinker %d: %s\n", loadcmd, dylinker);
348 : 6 : free(dylinker);
349 : 21 : break;
350 : : case LC_ID_DYLIB: // provides
351 : : case LC_LOAD_DYLIB: // requires...
352 : : case LC_LOAD_WEAK_DYLIB:
353 : : case LC_REEXPORT_DYLIB:
354 : : case LC_LAZY_LOAD_DYLIB:
355 : : case LC_LOAD_UPWARD_DYLIB:;
356 : 15 : dylib_t *dylib = 0;
357 [ + - + - ]: 30 : if ((x = read_dylib(fd, swap, loadcmdsize,
358 : 15 : &dylib)) < 0) {
359 : 0 : goto cleanup;
360 : : }
361 : 15 : n += x;
362 : : // while under Darwin full path references are recommended and ubiquitous,
363 : : // we align with pkg native environment and use only the basename
364 : : // this also strips off any @executable_path, @loader_path, @rpath components
365 : 15 : const char * basename = strrchr(dylib->path, '/');
366 [ + - ]: 15 : basename = basename ? basename + 1 : dylib->path;
367 : 15 : pkg_debug(3,
368 : : "Adding dynamic library path: %s ts %"PRIu32" current(%"PRIuFAST16", %"PRIuFAST16", %"PRIuFAST16") compat(%"PRIuFAST16", %"PRIuFAST16", %"PRIuFAST16")\n",
369 : 15 : dylib->path, dylib->timestamp,
370 : 15 : dylib->current_version.major,
371 : 15 : dylib->current_version.minor,
372 : 15 : dylib->current_version.patch,
373 : 15 : dylib->compatibility_version.major,
374 : 15 : dylib->compatibility_version.minor,
375 : 15 : dylib->compatibility_version.patch);
376 : :
377 : : char *lib_with_version;
378 [ + + ]: 15 : if (dylib->current_version.patch) {
379 : 1 : xasprintf(&lib_with_version, "%s-%"PRIuFAST16".%"PRIuFAST16".%"PRIuFAST16, basename, dylib->current_version.major, dylib->current_version.minor, dylib->current_version.patch);
380 : 1 : } else {
381 : 14 : xasprintf(&lib_with_version, "%s-%"PRIuFAST16".%"PRIuFAST16, basename, dylib->current_version.major, dylib->current_version.minor);
382 : : }
383 [ + + ]: 15 : if (LC_ID_DYLIB == loadcmd) {
384 : 3 : pkg_addshlib_provided(pkg, lib_with_version, PKG_SHLIB_FLAGS_NONE);
385 : 3 : } else {
386 : 12 : pkg_addshlib_required(pkg, lib_with_version, PKG_SHLIB_FLAGS_NONE);
387 : : }
388 : 15 : free(lib_with_version);
389 : 15 : free(dylib);
390 : 15 : break;
391 : : default:
392 : 110 : break;
393 : : }
394 : 131 : const uint32_t fill = loadcmdsize - (n - n0);
395 [ + + - + ]: 131 : if (fill && -1 == (x = lseek(fd, fill, SEEK_CUR))) {
396 : 0 : goto cleanup;
397 : : }
398 : 131 : n += fill;
399 [ + - ]: 131 : if (n > mh.sizeofcmds) {
400 : : // we passed the frame boundary of the load commands
401 : 0 : pkg_emit_error("Mach-O structure misread.");
402 : 0 : errno = EINVAL;
403 : 0 : goto cleanup;
404 : : }
405 : 9 : }
406 : :
407 : : cleanup:
408 : 9 : free(mf);
409 : 9 : return ret;
410 : 9 : }
411 : :
412 : : int
413 : 9 : pkg_analyse_init_macho(__unused const char *stage)
414 : : {
415 : 9 : return EPKG_OK;
416 : : }
417 : :
418 : : int
419 : 9 : pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fpath)
420 : : {
421 : 9 : int ret = EPKG_OK;
422 : 9 : pkg_debug(1, "Analysing Mach-O %s", fpath);
423 : :
424 : 9 : int fd = open(fpath, O_RDONLY);
425 [ - + ]: 9 : if (-1 == fd) {
426 : : // pkg_emit_errno("open_pkg_analyse_macho", fpath);
427 : : // ret = EPKG_FATAL;
428 : : // Be consistent with analyse_elf and return no error if fpath cannot be opened
429 : 0 : return ret;
430 : : } else {
431 : 9 : ret = analyse_macho(fd, pkg);
432 [ + - ]: 9 : if (-1 == close(fd)) {
433 : 0 : pkg_emit_errno("close_pkg_analyse_macho", fpath);
434 : 0 : ret = EPKG_FATAL;
435 : 0 : }
436 : : }
437 [ + - ]: 9 : if (developer_mode) {
438 [ # # # # ]: 0 : if (ret != EPKG_OK && ret != EPKG_END) {
439 : 0 : return EPKG_WARN;
440 : : }
441 : 0 : }
442 : 9 : return ret;
443 : 9 : }
444 : :
445 : : int
446 : 9 : pkg_analyse_close_macho()
447 : : {
448 : 9 : return EPKG_OK;
449 : : }
|