/** * Validate a target process' address pointer's availability via @a mobj, verifying that @a length bytes can be read * from @a mobj at @a address, and return the pointer from which a @a length read may be performed. * * @param mobj An initialized memory object. * @param address The base address to be read. This address should be relative to the target task's address space. * @param offset An offset to be applied to @a address prior to verifying the address range. * @param length The total number of bytes that should be readable at @a address. * * @return Returns the validated pointer, or NULL if the requested bytes are not within @a mobj's range. */ void *plcrash_async_mobject_remap_address (plcrash_async_mobject_t *mobj, pl_vm_address_t address, pl_vm_off_t offset, size_t length) { /* Map into our memory space */ pl_vm_address_t remapped = address - mobj->vm_slide; if (!plcrash_async_mobject_verify_local_pointer(mobj, (uintptr_t) remapped, offset, length)) return NULL; return (void *) remapped + offset; }
/** * Iterate over the available Mach-O LC_CMD entries. * * @param image The image to iterate * @param previous The previously returned LC_CMD address value, or 0 to iterate from the first LC_CMD. * @return Returns the address of the next load_command on success, or NULL on failure. * * @note A returned command is gauranteed to be readable, and fully within mapped address space. If the command * command can not be verified to have available MAX(sizeof(struct load_command), cmd->cmdsize) bytes, NULL will be * returned. */ void *plcrash_async_macho_next_command (plcrash_async_macho_t *image, void *previous) { struct load_command *cmd; /* On the first iteration, determine the LC_CMD offset from the Mach-O header. */ if (previous == NULL) { /* Sanity check */ if (image->byteorder->swap32(image->header.sizeofcmds) < sizeof(struct load_command)) { PLCF_DEBUG("Mach-O sizeofcmds is less than sizeof(struct load_command) in %s", image->name); return NULL; } return plcrash_async_mobject_remap_address(&image->load_cmds, image->header_addr, image->header_size, sizeof(struct load_command)); } /* We need the size from the previous load command; first, verify the pointer. */ cmd = previous; if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) cmd, 0, sizeof(*cmd))) { PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name); return NULL; } /* Advance to the next command */ uint32_t cmdsize = image->byteorder->swap32(cmd->cmdsize); void *next = ((uint8_t *)previous) + cmdsize; /* Avoid walking off the end of the cmd buffer */ if ((uintptr_t)next >= image->load_cmds.address + image->load_cmds.length) return NULL; /* Verify that it holds at least load_command */ if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) next, 0, sizeof(struct load_command))) { PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name); return NULL; } /* Verify the actual size. */ cmd = next; if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) next, 0, image->byteorder->swap32(cmd->cmdsize))) { PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name); return NULL; } return next; }
/** * Find the first LC_CMD matching the given @a cmd type. * * @param image The image to search. * @param expectedCommand The LC_CMD type to find. * * @return Returns the address of the matching load_command on success, or 0 on failure. * * @note A returned command is gauranteed to be readable, and fully within mapped address space. If the command * command can not be verified to have available MAX(sizeof(struct load_command), cmd->cmdsize) bytes, NULL will be * returned. */ void *plcrash_async_macho_find_command (plcrash_async_macho_t *image, uint32_t expectedCommand) { struct load_command *cmd = NULL; /* Iterate commands until we either find a match, or reach the end */ while ((cmd = plcrash_async_macho_next_command(image, cmd)) != NULL) { /* Read the load command type */ if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) cmd, 0, sizeof(*cmd))) { PLCF_DEBUG("Failed to map LC_CMD at address %p in: %s", cmd, image->name); return NULL; } /* Return a match */ if (image->byteorder->swap32(cmd->cmd) == expectedCommand) { return cmd; } } /* No match found */ return NULL; }
/** * Fetch the entry corresponding to @a index. * * @param reader The reader from which @a table was mapped. * @param symtab The symbol table to read. * @param index The index of the entry to return. * * @warning The implementation implements no bounds checking on @a index, and it is the caller's responsibility to ensure * that they do not read an invalid entry. */ plcrash_async_macho_symtab_entry_t plcrash_async_macho_symtab_reader_read (plcrash_async_macho_symtab_reader_t *reader, void *symtab, uint32_t index) { const plcrash_async_byteorder_t *byteorder = reader->image->byteorder; /* nlist_64 and nlist are identical other than the trailing address field, so we use * a union to share a common implementation of symbol lookup. The following asserts * provide a sanity-check of that assumption, in the case where this code is moved * to a new platform ABI. */ { #define pl_m_sizeof(type, field) sizeof(((type *)NULL)->field) PLCF_ASSERT(__offsetof(struct nlist_64, n_type) == __offsetof(struct nlist, n_type)); PLCF_ASSERT(pl_m_sizeof(struct nlist_64, n_type) == pl_m_sizeof(struct nlist, n_type)); PLCF_ASSERT(__offsetof(struct nlist_64, n_un.n_strx) == __offsetof(struct nlist, n_un.n_strx)); PLCF_ASSERT(pl_m_sizeof(struct nlist_64, n_un.n_strx) == pl_m_sizeof(struct nlist, n_un.n_strx)); PLCF_ASSERT(__offsetof(struct nlist_64, n_value) == __offsetof(struct nlist, n_value)); #undef pl_m_sizeof } #define pl_sym_value(image, nl) (image->m64 ? image->byteorder->swap64((nl)->n64.n_value) : image->byteorder->swap32((nl)->n32.n_value)) /* Perform 32-bit/64-bit dependent aliased pointer math. */ pl_nlist_common *symbol; if (reader->image->m64) { symbol = (pl_nlist_common *) &(((struct nlist_64 *) symtab)[index]); } else { symbol = (pl_nlist_common *) &(((struct nlist *) symtab)[index]); } plcrash_async_macho_symtab_entry_t entry = { .n_strx = byteorder->swap32(symbol->n32.n_un.n_strx), .n_type = symbol->n32.n_type, .n_sect = symbol->n32.n_sect, .n_desc = byteorder->swap16(symbol->n32.n_desc), .n_value = pl_sym_value(reader->image, symbol) }; entry.normalized_value = entry.n_value; /* Normalize the symbol address. We have to set the low-order bit ourselves for ARM THUMB functions. */ if (entry.n_desc & N_ARM_THUMB_DEF) entry.normalized_value = (entry.n_value|1); else entry.normalized_value = entry.n_value; #undef pl_sym_value return entry; } /** * Given a string table offset for @a reader, returns the pointer to the validated NULL terminated string, or returns * NULL if the string does not fall within the reader's mapped string table. * * @param reader The reader containing a mapped string table. * @param n_strx The index within the @a reader string table to a symbol name. */ const char *plcrash_async_macho_symtab_reader_symbol_name (plcrash_async_macho_symtab_reader_t *reader, uint32_t n_strx) { /* * It's possible, though unlikely, that the n_strx index value is invalid. To handle this, * we walk the string until \0 is hit, verifying that it can be found in its entirety within * * TODO: Evaluate effeciency of per-byte calling of plcrash_async_mobject_verify_local_pointer(). We should * probably validate whole pages at a time instead. */ const char *sym_name = reader->string_table + n_strx; const char *p = sym_name; do { if (!plcrash_async_mobject_verify_local_pointer(&reader->linkedit.mobj, (uintptr_t) p, 0, 1)) { PLCF_DEBUG("End of mobject reached while walking string\n"); return NULL; } p++; } while (*p != '\0'); return sym_name; } /** * Free all mapped reader resources. * * @note Unlike most free() functions in this API, this function is async-safe. */ void plcrash_async_macho_symtab_reader_free (plcrash_async_macho_symtab_reader_t *reader) { plcrash_async_macho_mapped_segment_free(&reader->linkedit); }
/** * Initialize a new Mach-O binary image parser. * * @param image The image structure to be initialized. * @param name The file name or path for the Mach-O image. * @param header The task-local address of the image's Mach-O header. * * @return PLCRASH_ESUCCESS on success. PLCRASH_EINVAL will be returned in the Mach-O file can not be parsed, * or PLCRASH_EINTERNAL if an error occurs reading from the target task. * * @warning This method is not async safe. */ plcrash_error_t plcrash_nasync_macho_init (plcrash_async_macho_t *image, mach_port_t task, const char *name, pl_vm_address_t header) { plcrash_error_t ret; /* Defaults checked in the error cleanup handler */ bool mobj_initialized = false; bool task_initialized = false; image->name = NULL; /* Basic initialization */ image->task = task; image->header_addr = header; image->name = strdup(name); mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, 1); task_initialized = true; /* Read in the Mach-O header */ kern_return_t kt; if ((kt = plcrash_async_read_addr(image->task, image->header_addr, &image->header, sizeof(image->header))) != KERN_SUCCESS) { /* NOTE: The image struct must be fully initialized before returning here, as otherwise our _free() function * will crash */ PLCF_DEBUG("Failed to read Mach-O header from 0x%" PRIx64 " for image %s, kern_error=%d", (uint64_t) image->header_addr, name, kt); ret = PLCRASH_EINTERNAL; goto error; } /* Set the default byte order*/ image->byteorder = &plcrash_async_byteorder_direct; /* Parse the Mach-O magic identifier. */ switch (image->header.magic) { case MH_CIGAM: // Enable byte swapping image->byteorder = &plcrash_async_byteorder_swapped; // Fall-through case MH_MAGIC: image->m64 = false; break; case MH_CIGAM_64: // Enable byte swapping image->byteorder = &plcrash_async_byteorder_swapped; // Fall-through case MH_MAGIC_64: image->m64 = true; break; case FAT_CIGAM: case FAT_MAGIC: PLCF_DEBUG("%s called with an unsupported universal Mach-O archive in: %s", __func__, image->name); return PLCRASH_EINVAL; break; default: PLCF_DEBUG("Unknown Mach-O magic: 0x%" PRIx32 " in: %s", image->header.magic, image->name); return PLCRASH_EINVAL; } /* Save the header size */ if (image->m64) { image->header_size = sizeof(struct mach_header_64); } else { image->header_size = sizeof(struct mach_header); } /* Map in header + load commands */ pl_vm_size_t cmd_len = image->byteorder->swap32(image->header.sizeofcmds); pl_vm_size_t cmd_offset = image->header_addr + image->header_size; image->ncmds = image->byteorder->swap32(image->header.ncmds); ret = plcrash_async_mobject_init(&image->load_cmds, image->task, cmd_offset, cmd_len, true); if (ret != PLCRASH_ESUCCESS) { PLCF_DEBUG("Failed to map Mach-O load commands in image %s", image->name); goto error; } else { mobj_initialized = true; } /* Now that the image has been sufficiently initialized, determine the __TEXT segment size */ void *cmdptr = NULL; image->text_size = 0x0; bool found_text_seg = false; while ((cmdptr = plcrash_async_macho_next_command_type(image, cmdptr, image->m64 ? LC_SEGMENT_64 : LC_SEGMENT)) != 0) { if (image->m64) { struct segment_command_64 *segment = cmdptr; if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) { PLCF_DEBUG("LC_SEGMENT command was too short"); ret = PLCRASH_EINVAL; goto error; } if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0) continue; image->text_size = image->byteorder->swap64(segment->vmsize); image->text_vmaddr = image->byteorder->swap64(segment->vmaddr); found_text_seg = true; break; } else { struct segment_command *segment = cmdptr; if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, (uintptr_t) segment, 0, sizeof(*segment))) { PLCF_DEBUG("LC_SEGMENT command was too short"); ret = PLCRASH_EINVAL; goto error; } if (plcrash_async_strncmp(segment->segname, SEG_TEXT, sizeof(segment->segname)) != 0) continue; image->text_size = image->byteorder->swap32(segment->vmsize); image->text_vmaddr = image->byteorder->swap32(segment->vmaddr); found_text_seg = true; break; } } if (!found_text_seg) { PLCF_DEBUG("Could not find __TEXT segment!"); ret = PLCRASH_EINVAL; goto error; } /* Compute the vmaddr slide */ if (image->text_vmaddr < header) { image->vmaddr_slide = header - image->text_vmaddr; } else if (image->text_vmaddr > header) { image->vmaddr_slide = -((pl_vm_off_t) (image->text_vmaddr - header)); } else { image->vmaddr_slide = 0; } return PLCRASH_ESUCCESS; error: if (mobj_initialized) plcrash_async_mobject_free(&image->load_cmds); if (image->name != NULL) free(image->name); if (task_initialized) mach_port_mod_refs(mach_task_self(), image->task, MACH_PORT_RIGHT_SEND, -1); return ret; }
/** * Find and map a named section within a named segment, initializing @a mobj. * It is the caller's responsibility to dealloc @a mobj after a successful * initialization * * @param image The image to search for @a segname. * @param segname The name of the segment to search. * @param sectname The name of the section to map. * @param mobj The mobject to be initialized with a mapping of the section's data. It is the caller's responsibility to dealloc @a mobj after * a successful initialization. * * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_ENOTFOUND if the section is not found, or an error result on failure. */ plcrash_error_t plcrash_async_macho_map_section (plcrash_async_macho_t *image, const char *segname, const char *sectname, plcrash_async_mobject_t *mobj) { struct segment_command *cmd_32; struct segment_command_64 *cmd_64; void *segment = plcrash_async_macho_find_segment_cmd(image, segname); if (segment == NULL) return PLCRASH_ENOTFOUND; cmd_32 = segment; cmd_64 = segment; uint32_t nsects; uintptr_t cursor = (uintptr_t) segment; if (image->m64) { nsects = image->byteorder->swap32(cmd_64->nsects); cursor += sizeof(*cmd_64); } else { nsects = image->byteorder->swap32(cmd_32->nsects); cursor += sizeof(*cmd_32); } for (uint32_t i = 0; i < nsects; i++) { struct section *sect_32 = NULL; struct section_64 *sect_64 = NULL; if (image->m64) { if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, cursor, 0, sizeof(*sect_64))) { PLCF_DEBUG("Section table entry outside of expected range; searching for (%s,%s)", segname, sectname); return PLCRASH_EINVAL; } sect_64 = (void *) cursor; cursor += sizeof(*sect_64); } else { if (!plcrash_async_mobject_verify_local_pointer(&image->load_cmds, cursor, 0, sizeof(*sect_32))) { PLCF_DEBUG("Section table entry outside of expected range; searching for (%s,%s)", segname, sectname); return PLCRASH_EINVAL; } sect_32 = (void *) cursor; cursor += sizeof(*sect_32); } const char *image_sectname = image->m64 ? sect_64->sectname : sect_32->sectname; if (plcrash_async_strncmp(sectname, image_sectname, sizeof(sect_64->sectname)) == 0) { /* Calculate the in-memory address and size */ pl_vm_address_t sectaddr; pl_vm_size_t sectsize; if (image->m64) { sectaddr = image->byteorder->swap64(sect_64->addr) + image->vmaddr_slide; sectsize = image->byteorder->swap32(sect_64->size); } else { sectaddr = image->byteorder->swap32(sect_32->addr) + image->vmaddr_slide; sectsize = image->byteorder->swap32(sect_32->size); } /* Perform and return the mapping */ return plcrash_async_mobject_init(mobj, image->task, sectaddr, sectsize, true); } } return PLCRASH_ENOTFOUND; }