/* * -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=c:et:sw=4:ts=4:sts=4:tw=100 * libmachista.c * * Copyright (c) 2011-2012, 2014, 2016-2018 The MacPorts Project * Copyright (c) 2011 Landon Fuller * Copyright (c) 2011 Clemens Lang * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include #endif /* required for asprintf(3) on macOS */ #define _DARWIN_C_SOURCE /* required for asprintf(3) on Linux */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #ifdef __MACH__ #include #include #include #include #endif #include "libmachista.h" #include "hashmap.h" #include "strlcpy.h" #ifdef __MACH__ /* Tiger compatibility */ #ifndef LC_RPATH #define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */ /* * The rpath_command contains a path which at runtime should be added to * the current run path used to find @rpath prefixed dylibs. */ struct rpath_command { uint32_t cmd; /* LC_RPATH */ uint32_t cmdsize; /* includes string */ union lc_str path; /* path to add to run path */ }; #endif #ifndef LC_REEXPORT_DYLIB #define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */ #endif #endif /* __MACH__ */ typedef struct macho_input { const void *data; size_t length; } macho_input_t; /* This is macho_handle_t. The corresponding typedef is in the header */ struct macho_handle { HashMap *result_map; }; #ifdef __MACH__ /* Verify that the given range is within bounds. */ static const void *macho_read (macho_input_t *input, const void *address, size_t length) { if ((((uint8_t *) address) - ((uint8_t *) input->data)) + length > input->length) { // warnx("Short read parsing Mach-O input"); return NULL; } return address; } /* Verify that address + offset + length is within bounds. */ static const void *macho_offset (macho_input_t *input, const void *address, size_t offset, size_t length) { void *result = ((uint8_t *) address) + offset; return macho_read(input, result, length); } #endif /* return a human readable formatted version number. the result must be free()'d. */ char *macho_format_dylib_version (uint32_t version) { char *result; asprintf(&result, "%"PRIu32".%"PRIu32".%"PRIu32, (version >> 16) & 0xFFFF, (version >> 8) & 0xFF, version & 0xFF); return result; } #ifdef __MACH__ const char *macho_get_arch_name (cpu_type_t cputype) { const NXArchInfo *archInfo = NXGetArchInfoFromCpuType(cputype, CPU_SUBTYPE_MULTIPLE); if (!archInfo) { return NULL; } return archInfo->name; #else const char *macho_get_arch_name (cpu_type_t cputype UNUSED) { return NULL; #endif } #ifdef __MACH__ /* Some byteswap wrappers */ static uint32_t macho_swap32 (uint32_t input) { return OSSwapInt32(input); } static uint32_t macho_nswap32(uint32_t input) { return input; } /* Creates a new macho_t. * Returns NULL on failure or a pointer to a 0-initialized macho_t on success */ static macho_t *create_macho_t (void) { macho_t *mt = malloc(sizeof(macho_t)); if (mt == NULL) return NULL; memset(mt, 0, sizeof(macho_t)); return mt; } /* Creates a new macho_arch_t. * Returns NULL on failure or a pointer to a 0-initialized macho_arch_t on success */ static macho_arch_t *create_macho_arch_t (void) { macho_arch_t *mat = malloc(sizeof(macho_arch_t)); if (mat == NULL) return NULL; memset(mat, 0, sizeof(macho_arch_t)); return mat; } /* Creates a new macho_loadcmd_t. * Returns NULL on failure or a pointer to a 0-initialized macho_loadcmd_t on success */ static macho_loadcmd_t *create_macho_loadcmd_t (void) { macho_loadcmd_t *mlt = malloc(sizeof(macho_loadcmd_t)); if (mlt == NULL) return NULL; memset(mlt, 0, sizeof(macho_loadcmd_t)); return mlt; } #endif /* Frees a previously allocated macho_loadcmd_t and all it's associated resources */ static void free_macho_loadcmd_t (macho_loadcmd_t *mlt) { if (mlt == NULL) return; free(mlt->mlt_install_name); free(mlt); } /* Frees a previously allocated macho_arch_t and all it's associated resources */ static void free_macho_arch_t (macho_arch_t *mat) { if (mat == NULL) return; macho_loadcmd_t *current = mat->mat_loadcmds; while (current != NULL) { macho_loadcmd_t *freeme = current; current = current->next; free_macho_loadcmd_t(freeme); } free(mat->mat_install_name); free(mat->mat_rpath); free(mat); } /* Frees a previously allocated macho_t and all it's associated resources */ static void free_macho_t (macho_t *mt) { if (mt == NULL) return; macho_arch_t *current = mt->mt_archs; while (current != NULL) { macho_arch_t *freeme = current; current = current->next; free_macho_arch_t(freeme); } free(mt); } #ifdef __MACH__ /* Creates a new element in the architecture list of a macho_t (mt_archs), increases the counter of * architectures (mt_arch_count) and returns a pointer to the newly allocated element or NULL on * error */ static macho_arch_t *macho_archlist_append (macho_t *mt) { macho_arch_t *old_head = mt->mt_archs; macho_arch_t *new_head = create_macho_arch_t(); if (new_head == NULL) return NULL; new_head->next = old_head; mt->mt_archs = new_head; return mt->mt_archs; } /* Creates a new element in the load command list of a macho_arch_t (mat_loadcmds), increases the * counter of load commands (mat_loadcmd_count) and returns a pointer to the newly allocated element * or NULL on error */ static macho_loadcmd_t *macho_loadcmdlist_append (macho_arch_t *mat) { macho_loadcmd_t *old_head = mat->mat_loadcmds; macho_loadcmd_t *new_head = create_macho_loadcmd_t(); if (new_head == NULL) return NULL; new_head->next = old_head; mat->mat_loadcmds = new_head; return mat->mat_loadcmds; } #endif /* Parse a Mach-O header */ #ifdef __MACH__ static int parse_macho (macho_t *mt, macho_input_t *input) { /* Read the file type. */ const uint32_t *magic = macho_read(input, input->data, sizeof(uint32_t)); if (magic == NULL) return MACHO_ERANGE; /* Parse the Mach-O header */ bool universal = false; uint32_t (*swap32)(uint32_t) = macho_nswap32; const struct mach_header *header; const struct mach_header_64 *header64; size_t header_size; const struct fat_header *fat_header; macho_arch_t *mat = NULL; switch (*magic) { case MH_CIGAM: swap32 = macho_swap32; // Fall-through case MH_MAGIC: header_size = sizeof(*header); header = macho_read(input, input->data, header_size); if (header == NULL) return MACHO_ERANGE; mat = macho_archlist_append(mt); if (mat == NULL) return MACHO_EMEM; /* 32-bit Mach-O */ mat->mat_arch = swap32(header->cputype); break; case MH_CIGAM_64: swap32 = macho_swap32; // Fall-through case MH_MAGIC_64: header_size = sizeof(*header64); header64 = macho_read(input, input->data, sizeof(*header64)); if (header64 == NULL) return MACHO_ERANGE; mat = macho_archlist_append(mt); if (mat == NULL) return MACHO_EMEM; /* The 64-bit header is a direct superset of the 32-bit header */ header = (struct mach_header *) header64; /* 64-bit Macho-O */ mat->mat_arch = swap32(header->cputype); break; case FAT_CIGAM: case FAT_MAGIC: fat_header = macho_read(input, input->data, sizeof(*fat_header)); universal = true; /* Universal binary */ break; default: /* Unknown binary type */ //warnx("Unknown Mach-O magic: 0x%" PRIx32 "", *magic); return MACHO_EMAGIC; } /* Parse universal file. */ if (universal) { uint32_t nfat = OSSwapBigToHostInt32(fat_header->nfat_arch); const struct fat_arch *archs = macho_offset(input, fat_header, sizeof(struct fat_header), sizeof(struct fat_arch)); if (archs == NULL) return MACHO_ERANGE; for (uint32_t i = 0; i < nfat; i++) { // foreach architecture const struct fat_arch *arch = macho_read(input, archs + i, sizeof(struct fat_arch)); if (arch == NULL) return MACHO_ERANGE; /* Fetch a pointer to the architecture's Mach-O header. */ macho_input_t arch_input; arch_input.length = OSSwapBigToHostInt32(arch->size); arch_input.data = macho_offset(input, input->data, OSSwapBigToHostInt32(arch->offset), arch_input.length); if (arch_input.data == NULL) return MACHO_ERANGE; /* Parse the architecture's Mach-O header */ int res = parse_macho(mt, &arch_input); if (res != MACHO_SUCCESS) return res; } return MACHO_SUCCESS; } /* Copy the architecture */ mat->mat_arch = swap32(header->cputype); /* Parse the Mach-O load commands */ uint32_t ncmds = swap32(header->ncmds); /* Setup to jump over the header on the first pass through instead of the previous command */ const struct load_command *cmd = (void *)header; uint32_t cmdsize = header_size; /* Iterate over the load commands */ for (uint32_t i = 0; i < ncmds; i++) { /* Load the next command */ cmd = macho_offset(input, cmd, cmdsize, sizeof(struct load_command)); if (cmd == NULL) return MACHO_ERANGE; /* Load the full command */ cmdsize = swap32(cmd->cmdsize); cmd = macho_read(input, cmd, cmdsize); if (cmd == NULL) return MACHO_ERANGE; /* Handle known types */ uint32_t cmd_type = swap32(cmd->cmd); switch (cmd_type) { case LC_RPATH: { /* Copy the rpath */ if (cmdsize < sizeof(struct rpath_command)) { //warnx("Incorrect cmd size"); return MACHO_ERANGE; } size_t pathlen = cmdsize - sizeof(struct rpath_command); const void *pathptr = macho_offset(input, cmd, sizeof(struct rpath_command), pathlen); if (pathptr == NULL) return MACHO_ERANGE; mat->mat_rpath = malloc(pathlen); if (mat->mat_rpath == NULL) return MACHO_EMEM; strlcpy(mat->mat_rpath, pathptr, pathlen); break; } case LC_ID_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_DYLIB: { const struct dylib_command *dylib_cmd = (const struct dylib_command *) cmd; /* Extract the install name */ if (cmdsize < sizeof(struct dylib_command)) { //warnx("Incorrect name size"); return MACHO_ERANGE; } size_t namelen = cmdsize - sizeof(struct dylib_command); const void *nameptr = macho_offset(input, cmd, sizeof(struct dylib_command), namelen); if (nameptr == NULL) return MACHO_ERANGE; if (cmd_type == LC_ID_DYLIB) { /* Copy install name */ mat->mat_install_name = malloc(namelen); if (mat->mat_install_name == NULL) return MACHO_EMEM; strlcpy(mat->mat_install_name, nameptr, namelen); /* Copy version numbers (raw, for easier comparison) */ mat->mat_version = swap32(dylib_cmd->dylib.current_version); mat->mat_comp_version = swap32(dylib_cmd->dylib.compatibility_version); } else { /* Append loadcmd to list of loadcommands */ macho_loadcmd_t *mlt = macho_loadcmdlist_append(mat); if (mlt == NULL) return MACHO_EMEM; /* Copy install name */ mlt->mlt_install_name = malloc(namelen); if (mlt->mlt_install_name == NULL) return MACHO_EMEM; strlcpy(mlt->mlt_install_name, nameptr, namelen); /* Copy version numbers (raw, for easier comparison) */ mlt->mlt_version = swap32(dylib_cmd->dylib.current_version); mlt->mlt_comp_version = swap32(dylib_cmd->dylib.compatibility_version); /* Copy command type */ mlt->mlt_type = cmd_type; } break; } default: break; } } return MACHO_SUCCESS; } #endif /* Parse a (possible Mach-O) file. For a more detailed description, see the header */ #ifdef __MACH__ int macho_parse_file(macho_handle_t *handle, const char *filepath, const macho_t **res) { int fd; struct stat st; void *data; macho_input_t input_file; /* Check hashmap for precomputed results */ const macho_t *cached_res = hashMapGet(handle->result_map, filepath); if (cached_res != NULL) { *res = cached_res; return MACHO_SUCCESS; } /* Open input file */ if ((fd = open(filepath, O_RDONLY)) < 0) { #ifdef HAVE__DYLD_SHARED_CACHE_CONTAINS_PATH /* All systems that have this function in the SDK should also have __builtin_available, so not bothering to check for it. */ if (__builtin_available(macos 11.0, ios 14.0, watchos 7.0, tvos 14.0, *)) { if (_dyld_shared_cache_contains_path(filepath)) { return MACHO_ECACHE; } } #endif /* HAVE__DYLD_SHARED_CACHE_CONTAINS_PATH */ return MACHO_EFILE; } /* Get file length */ if (fstat(fd, &st) != 0) { close(fd); return MACHO_EFILE; } /* Map file into address space */ if ((data = mmap(NULL, st.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) { close(fd); return MACHO_EMMAP; } /* Parse file */ input_file.data = data; input_file.length = st.st_size; *res = create_macho_t(); if (*res == NULL) return MACHO_EMEM; /* The output parameter *res should be read-only for the user of the lib only, but writable for * us */ int ret = parse_macho((macho_t *)*res, &input_file); if (ret == MACHO_SUCCESS) { /* Insert into hashmap for caching */ if (0 == hashMapPut(handle->result_map, filepath, *res, NULL)) { free_macho_t((macho_t *)*res); *res = NULL; ret = MACHO_EMEM; } } else { /* An error occurred, free mt */ free_macho_t((macho_t *)*res); *res = NULL; } /* Cleanup */ munmap(data, st.st_size); close(fd); return ret; #else int macho_parse_file(macho_handle_t *handle UNUSED, const char *filepath UNUSED, const macho_t **res UNUSED) { return 0; #endif } /* Create a new macho_handle_t. More information on this function is available in the header */ macho_handle_t *macho_create_handle (void) { macho_handle_t *mht = malloc(sizeof(macho_handle_t)); if (mht == NULL) return NULL; mht->result_map = hashMapCreate((void (*)(const void *))free_macho_t); if (mht->result_map == NULL) { free(mht); return NULL; } return mht; } /* Release a macho_handle_t. For more documentation, see the header */ void macho_destroy_handle(macho_handle_t *handle) { if (handle == NULL) return; hashMapDestroy(handle->result_map); free(handle); } /* Returns string representation of the MACHO_* error code constants */ const char *macho_strerror(int err) { int num; #ifdef HAVE_FLS num = fls(err); #else /* Tiger compatibility, see #42186 */ num = 0; while (err > 0) { err >>= 1; num++; } #endif static char *errors[] = { /* 0x00 */ "Success", /* 0x01 */ "Error opening or reading file", /* 0x02 */ "Error mapping file into memory", /* 0x04 */ "Error allocating memory", /* 0x08 */ "Premature end of data, possibly corrupt file", /* 0x10 */ "Not a Mach-O file", /* 0x20 */ "Shared cache only", }; return errors[num]; }