diff --git a/Sources/KSCrashRecordingCore/KSCxaThrowSwapper.c b/Sources/KSCrashRecordingCore/KSCxaThrowSwapper.c index 647f04af3..9d39e420a 100644 --- a/Sources/KSCrashRecordingCore/KSCxaThrowSwapper.c +++ b/Sources/KSCrashRecordingCore/KSCxaThrowSwapper.c @@ -51,6 +51,9 @@ #include "KSCxaThrowSwapper.h" #include +#include +#include +#include #include #include #include @@ -60,12 +63,9 @@ #include #include -#include "KSgetsect.h" +#include "KSMach-O.h" #include "KSPlatformSpecificDefines.h" - -#ifndef SEG_DATA_CONST -#define SEG_DATA_CONST "__DATA_CONST" -#endif +#include "KSLogger.h" typedef struct { @@ -82,16 +82,25 @@ static size_t g_cxa_originals_count = 0; static void addPair(KSAddressPair pair) { + KSLOG_DEBUG("Adding address pair: image=%p, function=%p", (void *)pair.image, (void *)pair.function); + if (g_cxa_originals_count == g_cxa_originals_capacity) { g_cxa_originals_capacity *= 2; g_cxa_originals = (KSAddressPair *) realloc(g_cxa_originals, sizeof(KSAddressPair) * g_cxa_originals_capacity); + if (g_cxa_originals == NULL) + { + KSLOG_ERROR("Failed to realloc memory for g_cxa_originals: %s", strerror(errno)); + return; + } } memcpy(&g_cxa_originals[g_cxa_originals_count++], &pair, sizeof(KSAddressPair)); } static uintptr_t findAddress(void *address) { + KSLOG_TRACE("Finding address for %p", address); + for (size_t i = 0; i < g_cxa_originals_count; i++) { if (g_cxa_originals[i].image == (uintptr_t) address) @@ -99,6 +108,7 @@ static uintptr_t findAddress(void *address) return g_cxa_originals[i].function; } } + KSLOG_WARN("Address %p not found", address); return (uintptr_t) NULL; } @@ -106,6 +116,8 @@ static void __cxa_throw_decorator(void *thrown_exception, void *tinfo, void (*de { const int k_requiredFrames = 2; + KSLOG_TRACE("Decorating __cxa_throw"); + g_cxa_throw_handler(thrown_exception, tinfo, dest); void *backtraceArr[k_requiredFrames]; @@ -119,6 +131,7 @@ static void __cxa_throw_decorator(void *thrown_exception, void *tinfo, void (*de uintptr_t function = findAddress(info.dli_fbase); if (function != (uintptr_t) NULL) { + KSLOG_TRACE("Calling original __cxa_throw function at %p", (void *)function); cxa_throw_type original = (cxa_throw_type) function; original(thrown_exception, tinfo, dest); } @@ -126,139 +139,31 @@ static void __cxa_throw_decorator(void *thrown_exception, void *tinfo, void (*de } } -static vm_prot_t get_protection(void *sectionStart) -{ - mach_port_t task = mach_task_self(); - vm_size_t size = 0; - vm_address_t address = (vm_address_t) sectionStart; - memory_object_name_t object; -#if __LP64__ - mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; - vm_region_basic_info_data_64_t info; - kern_return_t info_ret = - vm_region_64(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t) &info, &count, &object); -#else - mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; - vm_region_basic_info_data_t info; - kern_return_t info_ret = - vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); -#endif - if (info_ret == KERN_SUCCESS) - { - return info.protection; - } - else - { - return VM_PROT_READ; - } -} - -static bool get_commands(const struct mach_header *header, - const struct symtab_command **symtab_cmd, - const struct dysymtab_command **dysymtab_cmd) -{ - segment_command_t *cur_seg_cmd = NULL; - uintptr_t cur = (uintptr_t) header + sizeof(mach_header_t); - - const struct symtab_command *local_symtab_cmd = NULL; - const struct dysymtab_command *local_dysymtab_cmd = NULL; - - for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) - { - cur_seg_cmd = (segment_command_t *) cur; - switch (cur_seg_cmd->cmd) - { - case LC_SYMTAB: - local_symtab_cmd = (const struct symtab_command *) cur_seg_cmd; - if (symtab_cmd != NULL) - { - *symtab_cmd = local_symtab_cmd; - } - break; - case LC_DYSYMTAB: - local_dysymtab_cmd = (const struct dysymtab_command *) cur_seg_cmd; - if (dysymtab_cmd != NULL) - { - *dysymtab_cmd = local_dysymtab_cmd; - } - break; - default: - break; - } - - if (local_symtab_cmd != NULL && - local_dysymtab_cmd != NULL && - local_dysymtab_cmd->nindirectsyms != 0) - { - return true; - } - } - return false; -} - -static bool get_sections(const segment_command_t *data_seg, - const section_t **lazy_sym_sect, - const section_t **non_lazy_sym_sect) -{ - if (strcmp(data_seg->segname, SEG_DATA) != 0 && - strcmp(data_seg->segname, SEG_DATA_CONST) != 0) - { - return false; - } - - const section_t *local_lazy_sym_sect = NULL; - const section_t *local_non_lazy_sym_sect = NULL; - - uintptr_t cur = (uintptr_t) data_seg; - const section_t *sect = NULL; - for (uint j = 0; j < data_seg->nsects; j++) - { - sect = (const section_t *) (cur + sizeof(segment_command_t)) + j; - const uint32_t section_type = sect->flags & SECTION_TYPE; - switch (section_type) - { - case S_LAZY_SYMBOL_POINTERS: - local_lazy_sym_sect = sect; - if (lazy_sym_sect != NULL) - { - *lazy_sym_sect = local_lazy_sym_sect; - } - break; - case S_NON_LAZY_SYMBOL_POINTERS: - local_non_lazy_sym_sect = sect; - if (non_lazy_sym_sect != NULL) - { - *non_lazy_sym_sect = local_non_lazy_sym_sect; - } - break; - default: - break; - } - if (local_non_lazy_sym_sect != NULL && local_lazy_sym_sect != NULL) - { - return true; - } - } - - return false; -} - -static void perform_rebinding_with_section(const section_t *section, +static void perform_rebinding_with_section(const section_t *dataSection, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) { - const bool isDataConst = strcmp(section->segname, SEG_DATA_CONST) == 0; - uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; - void **indirect_symbol_bindings = (void **) ((uintptr_t) slide + section->addr); + KSLOG_TRACE("Performing rebinding with section %s,%s", dataSection->segname, dataSection->sectname); + + const bool isDataConst = strcmp(dataSection->segname, SEG_DATA_CONST) == 0; + uint32_t *indirect_symbol_indices = indirect_symtab + dataSection->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + dataSection->addr); vm_prot_t oldProtection = VM_PROT_READ; if (isDataConst) { - oldProtection = get_protection(indirect_symbol_bindings); - mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE); + oldProtection = ksmacho_getSectionProtection(indirect_symbol_bindings); + if (mprotect(indirect_symbol_bindings, dataSection->size, PROT_READ | PROT_WRITE) != 0) + { + KSLOG_DEBUG("mprotect failed to set PROT_READ | PROT_WRITE for section %s,%s: %s", + dataSection->segname, + dataSection->sectname, + strerror(errno)); + return; + } } - for (uint i = 0; i < section->size / sizeof(void *); i++) + for (uint i = 0; i < dataSection->size / sizeof(void *); i++) { uint32_t symtab_index = indirect_symbol_indices[i]; if (symtab_index == INDIRECT_SYMBOL_ABS || @@ -273,12 +178,12 @@ static void perform_rebinding_with_section(const section_t *section, if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], g_cxa_throw_name) == 0) { Dl_info info; - if (dladdr(section, &info) != 0) + if (dladdr(dataSection, &info) != 0) { - KSAddressPair pair = {(uintptr_t) info.dli_fbase, (uintptr_t) indirect_symbol_bindings[i]}; + KSAddressPair pair = {(uintptr_t)info.dli_fbase, (uintptr_t)indirect_symbol_bindings[i]}; addPair(pair); } - indirect_symbol_bindings[i] = (void *) __cxa_throw_decorator; + indirect_symbol_bindings[i] = (void *)__cxa_throw_decorator; continue; } } @@ -297,65 +202,100 @@ static void perform_rebinding_with_section(const section_t *section, { protection |= PROT_EXEC; } - mprotect(indirect_symbol_bindings, section->size, protection); + if (mprotect(indirect_symbol_bindings, dataSection->size, protection) != 0) + { + KSLOG_ERROR("mprotect failed to restore protection for section %s,%s: %s", + dataSection->segname, + dataSection->sectname, + strerror(errno)); + } + } +} + +static void process_segment(const struct mach_header *header, + intptr_t slide, + const char *segname, + nlist_t *symtab, + char *strtab, + uint32_t *indirect_symtab) +{ + KSLOG_DEBUG("Processing segment %s", segname); + + const segment_command_t *segment = ksmacho_getSegmentByNameFromHeader((mach_header_t *)header, segname); + if (segment != NULL) + { + const section_t *lazy_sym_sect = ksmacho_getSectionByTypeFlagFromSegment(segment, S_LAZY_SYMBOL_POINTERS); + const section_t *non_lazy_sym_sect = ksmacho_getSectionByTypeFlagFromSegment(segment, S_NON_LAZY_SYMBOL_POINTERS); + + if (lazy_sym_sect != NULL) + { + perform_rebinding_with_section(lazy_sym_sect, slide, symtab, strtab, indirect_symtab); + } + if (non_lazy_sym_sect != NULL) + { + perform_rebinding_with_section(non_lazy_sym_sect, slide, symtab, strtab, indirect_symtab); + } + } + else + { + KSLOG_WARN("Segment %s not found", segname); } } static void rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) { + KSLOG_TRACE("Rebinding symbols for image with slide %p", (void *)slide); + Dl_info info; - if (dladdr(header, &info) == 0) + if (dladdr(header, &info) == 0) { + KSLOG_WARN("dladdr failed"); + return; + } + KSLOG_DEBUG("Image name: %s", info.dli_fname); + if (slide == 0) { + KSLOG_DEBUG("Zero slide, can't do anything with it"); return; } - const struct symtab_command *symtab_cmd = NULL; - const struct dysymtab_command *dysymtab_cmd = NULL; - if (get_commands(header, &symtab_cmd, &dysymtab_cmd) == false) + const struct symtab_command *symtab_cmd = + (struct symtab_command *)ksmacho_getCommandByTypeFromHeader((const mach_header_t *)header, LC_SYMTAB); + const struct dysymtab_command *dysymtab_cmd = + (struct dysymtab_command *)ksmacho_getCommandByTypeFromHeader((const mach_header_t *)header, LC_DYSYMTAB); + const segment_command_t *linkedit_segment = + ksmacho_getSegmentByNameFromHeader((mach_header_t *)header, SEG_LINKEDIT); + + if (symtab_cmd == NULL || dysymtab_cmd == NULL || linkedit_segment == NULL) { + KSLOG_WARN("Required commands or segments not found"); return; } - - const segment_command_t *linkedit_segment = ksgs_getsegbynamefromheader((mach_header_t *) header, SEG_LINKEDIT); - + // Find base symbol/string table addresses - uintptr_t linkedit_base = (uintptr_t) slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; - nlist_t *symtab = (nlist_t *) (linkedit_base + symtab_cmd->symoff); - char *strtab = (char *) (linkedit_base + symtab_cmd->stroff); + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); // Get indirect symbol table (array of uint32_t indices into symbol table) - uint32_t *indirect_symtab = (uint32_t *) (linkedit_base + dysymtab_cmd->indirectsymoff); + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); - const section_t *lazy_sym_sect = NULL; - const section_t *non_lazy_sym_sect = NULL; - - const segment_command_t *data_seg = ksgs_getsegbynamefromheader((mach_header_t *) header, SEG_DATA); - if (data_seg != NULL) - { - if (get_sections(data_seg, &lazy_sym_sect, &non_lazy_sym_sect)) - { - perform_rebinding_with_section(lazy_sym_sect, slide, symtab, strtab, indirect_symtab); - perform_rebinding_with_section(non_lazy_sym_sect, slide, symtab, strtab, indirect_symtab); - } - } - - const segment_command_t *data_const_seg = ksgs_getsegbynamefromheader((mach_header_t *) header, SEG_DATA_CONST); - if (data_const_seg != NULL) - { - if (get_sections(data_const_seg, &lazy_sym_sect, &non_lazy_sym_sect)) - { - perform_rebinding_with_section(lazy_sym_sect, slide, symtab, strtab, indirect_symtab); - perform_rebinding_with_section(non_lazy_sym_sect, slide, symtab, strtab, indirect_symtab); - } - } + process_segment(header, slide, SEG_DATA, symtab, strtab, indirect_symtab); + process_segment(header, slide, SEG_DATA_CONST, symtab, strtab, indirect_symtab); } int ksct_swap(const cxa_throw_type handler) { + KSLOG_DEBUG("Swapping __cxa_throw handler"); + if (g_cxa_originals == NULL) { g_cxa_originals_capacity = 25; - g_cxa_originals = (KSAddressPair *) malloc(sizeof(KSAddressPair) * g_cxa_originals_capacity); + g_cxa_originals = (KSAddressPair *)malloc(sizeof(KSAddressPair) * g_cxa_originals_capacity); + if (g_cxa_originals == NULL) + { + KSLOG_ERROR("Failed to allocate memory for g_cxa_originals: %s", strerror(errno)); + return -1; + } } g_cxa_originals_count = 0; diff --git a/Sources/KSCrashRecordingCore/KSMach-O.c b/Sources/KSCrashRecordingCore/KSMach-O.c new file mode 100644 index 000000000..0aee67362 --- /dev/null +++ b/Sources/KSCrashRecordingCore/KSMach-O.c @@ -0,0 +1,165 @@ +// +// KSMach-O.c +// +// Copyright (c) 2019 YANDEX LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall remain in place +// in this source code. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Contains code of getsegbyname.c +// https://opensource.apple.com/source/cctools/cctools-921/libmacho/getsegbyname.c.auto.html +// Copyright (c) 1999 Apple Computer, Inc. All rights reserved. +// +// @APPLE_LICENSE_HEADER_START@ +// +// This file contains Original Code and/or Modifications of Original Code +// as defined in and that are subject to the Apple Public Source License +// Version 2.0 (the 'License'). You may not use this file except in +// compliance with the License. Please obtain a copy of the License at +// http://www.opensource.apple.com/apsl/ and read it before using this +// file. +// +// The Original Code and all software distributed under the License are +// distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +// INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +// Please see the License for the specific language governing rights and +// limitations under the License. +// +// @APPLE_LICENSE_HEADER_END@ +// + +#include "KSMach-O.h" + +#include "KSLogger.h" +#include +#include +#include + +const struct load_command* ksmacho_getCommandByTypeFromHeader(const mach_header_t* header, uint32_t commandType) +{ + KSLOG_TRACE("Getting command by type %u in Mach header at %p", commandType, header); + + if (header == NULL) + { + KSLOG_ERROR("Header is NULL"); + return NULL; + } + + uintptr_t current = (uintptr_t)header + sizeof(mach_header_t); + struct load_command* loadCommand = NULL; + + for (uint commandIndex = 0; commandIndex < header->ncmds; commandIndex++) + { + loadCommand = (struct load_command*)current; + if (loadCommand->cmd == commandType) + { + return loadCommand; + } + current += loadCommand->cmdsize; + } + KSLOG_WARN("Command type %u not found", commandType); + return NULL; +} + +const segment_command_t* ksmacho_getSegmentByNameFromHeader(const mach_header_t* header, const char* segmentName) +{ + KSLOG_TRACE("Searching for segment %s in Mach header at %p", segmentName, header); + + if (header == NULL) + { + KSLOG_ERROR("Header is NULL"); + return NULL; + } + + const segment_command_t* segmentCommand; + unsigned long commandIndex; + + segmentCommand = (segment_command_t*)((uintptr_t)header + sizeof(mach_header_t)); + for (commandIndex = 0; commandIndex < header->ncmds; commandIndex++) + { + if (segmentCommand->cmd == LC_SEGMENT_ARCH_DEPENDENT && + strncmp(segmentCommand->segname, segmentName, sizeof(segmentCommand->segname)) == 0) + { + KSLOG_DEBUG("Segment %s found at %p", segmentName, segmentCommand); + return segmentCommand; + } + segmentCommand = (segment_command_t*)((uintptr_t)segmentCommand + segmentCommand->cmdsize); + } + + KSLOG_WARN("Segment %s not found in Mach header at %p", segmentName, header); + return NULL; +} + +const section_t* ksmacho_getSectionByTypeFlagFromSegment(const segment_command_t* segmentCommand, uint32_t flag) +{ + KSLOG_TRACE("Getting section by flag %u in segment %s", flag, segmentCommand->segname); + + if (segmentCommand == NULL) + { + KSLOG_ERROR("Segment is NULL"); + return NULL; + } + + uintptr_t current = (uintptr_t)segmentCommand + sizeof(segment_command_t); + const section_t* section = NULL; + + for (uint sectionIndex = 0; sectionIndex < segmentCommand->nsects; sectionIndex++) + { + section = (const section_t*)(current + sectionIndex * sizeof(section_t)); + if ((section->flags & SECTION_TYPE) == flag) + { + return section; + } + } + + KSLOG_DEBUG("Section with flag %u not found in segment %s", flag, segmentCommand->segname); + return NULL; +} + +vm_prot_t ksmacho_getSectionProtection(void* sectionStart) +{ + KSLOG_TRACE("Getting protection for section starting at %p", sectionStart); + + mach_port_t task = mach_task_self(); + vm_size_t size = 0; + vm_address_t address = (vm_address_t)sectionStart; + memory_object_name_t object; +#if __LP64__ + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + vm_region_basic_info_data_64_t info; + kern_return_t info_ret = + vm_region_64(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object); +#else + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; + vm_region_basic_info_data_t info; + kern_return_t info_ret = + vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); +#endif + if (info_ret == KERN_SUCCESS) + { + KSLOG_DEBUG("Protection obtained: %d", info.protection); + return info.protection; + } + else + { + KSLOG_ERROR("Failed to get protection for section: %s", mach_error_string(info_ret)); + return VM_PROT_READ; + } +} diff --git a/Sources/KSCrashRecordingCore/KSgetsect.c b/Sources/KSCrashRecordingCore/KSgetsect.c deleted file mode 100644 index ebce26051..000000000 --- a/Sources/KSCrashRecordingCore/KSgetsect.c +++ /dev/null @@ -1,66 +0,0 @@ -// -// KSgetsect.c -// -// Copyright (c) 2019 YANDEX LLC. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall remain in place -// in this source code. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// Modification of getsegbyname.c -// https://opensource.apple.com/source/cctools/cctools-921/libmacho/getsegbyname.c.auto.html -// Copyright (c) 1999 Apple Computer, Inc. All rights reserved. -// -// @APPLE_LICENSE_HEADER_START@ -// -// This file contains Original Code and/or Modifications of Original Code -// as defined in and that are subject to the Apple Public Source License -// Version 2.0 (the 'License'). You may not use this file except in -// compliance with the License. Please obtain a copy of the License at -// http://www.opensource.apple.com/apsl/ and read it before using this -// file. -// -// The Original Code and all software distributed under the License are -// distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER -// EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, -// INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. -// Please see the License for the specific language governing rights and -// limitations under the License. -// -// @APPLE_LICENSE_HEADER_END@ -// - -#include "KSgetsect.h" -#include - -const segment_command_t *ksgs_getsegbynamefromheader(const mach_header_t *header, const char *seg_name) -{ - segment_command_t *sgp; - unsigned long i; - - sgp = (segment_command_t *) ((uintptr_t) header + sizeof(mach_header_t)); - for (i = 0; i < header->ncmds; i++) - { - if (sgp->cmd == LC_SEGMENT_ARCH_DEPENDENT && strncmp(sgp->segname, seg_name, sizeof(sgp->segname)) == 0) - { - return sgp; - } - sgp = (segment_command_t *) ((uintptr_t) sgp + sgp->cmdsize); - } - return (segment_command_t *) NULL; -} diff --git a/Sources/KSCrashRecordingCore/include/KSMach-O.h b/Sources/KSCrashRecordingCore/include/KSMach-O.h new file mode 100644 index 000000000..0f307653f --- /dev/null +++ b/Sources/KSCrashRecordingCore/include/KSMach-O.h @@ -0,0 +1,79 @@ +// +// KSgetsect.h +// +// Copyright (c) 2019 YANDEX LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall remain in place +// in this source code. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#ifndef KSMachO_h +#define KSMachO_h + +#include "KSPlatformSpecificDefines.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * This routine returns the `load_command` structure for the specified command type + * if it exists in the passed mach header. Otherwise, it returns `NULL`. + * + * @param header Pointer to the mach_header structure. + * @param command_type The type of the command to search for. + * @return Pointer to the `load_command` structure if found, otherwise `NULL`. + */ +const struct load_command* ksmacho_getCommandByTypeFromHeader(const mach_header_t* header, uint32_t command_type); + +/** + * This routine returns the `segment_command` structure for the named segment + * if it exists in the passed mach header. Otherwise, it returns `NULL`. + * It just looks through the load commands. Since these are mapped into the text + * segment, they are read-only and thus const. + * + * @param header Pointer to the mach_header structure. + * @param seg_name The name of the segment to search for. + * @return Pointer to the `segment_command` structure if found, otherwise `NULL`. + */ +const segment_command_t* ksmacho_getSegmentByNameFromHeader(const mach_header_t* header, const char* seg_name); + +/** + * This routine returns the section structure for the specified `SECTION_TYPE` flag + * from mach-o/loader.h if it exists in the passed segment command. Otherwise, it returns `NULL`. + * + * @param dataSegment Pointer to the segment_command structure. + * @param flag The `SECTION_TYPE` flag of the section to search for. + * @return Pointer to the section structure if found, otherwise `NULL`. + */ +const section_t* ksmacho_getSectionByTypeFlagFromSegment(const segment_command_t* dataSegment, uint32_t flag); + +/** + * This routine returns the protection attributes for a given memory section. + * + * @param sectionStart Pointer to the start of the memory section. + * @return Protection attributes of the section. + */ +vm_prot_t ksmacho_getSectionProtection(void* sectionStart); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* KSMachO_h */ diff --git a/Sources/KSCrashRecordingCore/include/KSPlatformSpecificDefines.h b/Sources/KSCrashRecordingCore/include/KSPlatformSpecificDefines.h index 57fbd4311..144aedf1b 100644 --- a/Sources/KSCrashRecordingCore/include/KSPlatformSpecificDefines.h +++ b/Sources/KSCrashRecordingCore/include/KSPlatformSpecificDefines.h @@ -42,4 +42,8 @@ typedef struct nlist nlist_t; #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT #endif /* __LP64__ */ +#ifndef SEG_DATA_CONST +#define SEG_DATA_CONST "__DATA_CONST" +#endif /* SEG_DATA_CONST */ + #endif /* KSPlatformSpecificDefines_h */ diff --git a/Sources/KSCrashRecordingCore/include/KSgetsect.h b/Sources/KSCrashRecordingCore/include/KSgetsect.h deleted file mode 100644 index 7dc1505b3..000000000 --- a/Sources/KSCrashRecordingCore/include/KSgetsect.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// KSgetsect.h -// -// Copyright (c) 2019 YANDEX LLC. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall remain in place -// in this source code. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -#ifndef KSgetsect_h -#define KSgetsect_h - -#include -#include "KSPlatformSpecificDefines.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -/** - * This routine returns the segment_command structure for the named segment - * if it exist in the passed mach header. Otherwise it returns zero. - * It just looks through the load commands. Since these are mapped into the text - * segment they are read only and thus const. - */ -const segment_command_t *ksgs_getsegbynamefromheader(const mach_header_t *header, const char *seg_name); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* KSgetsect_h */ diff --git a/Tests/KSCrashRecordingCoreTests/KSMach-O_Tests.m b/Tests/KSCrashRecordingCoreTests/KSMach-O_Tests.m new file mode 100644 index 000000000..51a54b565 --- /dev/null +++ b/Tests/KSCrashRecordingCoreTests/KSMach-O_Tests.m @@ -0,0 +1,364 @@ +// +// KSMach-O_Tests.m +// +// +// Created by Gleb Linnik on 24.05.2024. +// + +#import "KSMach-O.h" +#import +#import + +@interface KSMach_O_Tests : XCTestCase +@end + +@implementation KSMach_O_Tests + +- (void)testGetSegmentByNameFromHeader_TextSegment +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a segment + segment_command_t seg1; + seg1.cmd = LC_SEGMENT_ARCH_DEPENDENT; + seg1.cmdsize = sizeof(segment_command_t); + strcpy(seg1.segname, "__TEXT"); + + // Copy the segment into the header memory + uint8_t buffer[sizeof(header) + sizeof(seg1)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &seg1, sizeof(seg1)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the segment is found correctly + const segment_command_t* result = ksmacho_getSegmentByNameFromHeader(testHeader, "__TEXT"); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(strcmp(result->segname, "__TEXT"), 0); +} + +- (void)testGetSegmentByNameFromHeader_DataSegment +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a segment + segment_command_t seg2; + seg2.cmd = LC_SEGMENT_ARCH_DEPENDENT; + seg2.cmdsize = sizeof(segment_command_t); + strcpy(seg2.segname, "__DATA"); + + // Copy the segment into the header memory + uint8_t buffer[sizeof(header) + sizeof(seg2)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &seg2, sizeof(seg2)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the segment is found correctly + const segment_command_t* result = ksmacho_getSegmentByNameFromHeader(testHeader, "__DATA"); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(strcmp(result->segname, "__DATA"), 0); +} + +- (void)testGetSegmentByNameFromHeader_NotFound +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a segment + segment_command_t seg1; + seg1.cmd = LC_SEGMENT_ARCH_DEPENDENT; + seg1.cmdsize = sizeof(segment_command_t); + strcpy(seg1.segname, "__TEXT"); + + // Copy the segment into the header memory + uint8_t buffer[sizeof(header) + sizeof(seg1)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &seg1, sizeof(seg1)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the segment is not found for an invalid name + const segment_command_t* result = ksmacho_getSegmentByNameFromHeader(testHeader, "__INVALID"); + XCTAssertEqual(result, NULL); +} + +- (void)testGetSegmentByNameFromHeader_InvalidHeader +{ + // Test with an invalid header + const segment_command_t* result = ksmacho_getSegmentByNameFromHeader(NULL, "__TEXT"); + XCTAssertEqual(result, NULL); +} + +- (void)testGetCommandByTypeFromHeader_SegmentArchDependent +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a command + struct load_command cmd1; + cmd1.cmd = LC_SEGMENT_ARCH_DEPENDENT; + cmd1.cmdsize = sizeof(struct load_command); + + // Copy the command into the header memory + uint8_t buffer[sizeof(header) + sizeof(cmd1)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &cmd1, sizeof(cmd1)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the command is found correctly + const struct load_command* result = ksmacho_getCommandByTypeFromHeader(testHeader, LC_SEGMENT_ARCH_DEPENDENT); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(result->cmd, LC_SEGMENT_ARCH_DEPENDENT); +} + +- (void)testGetCommandByTypeFromHeader_Symtab +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a command + struct load_command cmd2; + cmd2.cmd = LC_SYMTAB; + cmd2.cmdsize = sizeof(struct load_command); + + // Copy the command into the header memory + uint8_t buffer[sizeof(header) + sizeof(cmd2)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &cmd2, sizeof(cmd2)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the command is found correctly + const struct load_command* result = ksmacho_getCommandByTypeFromHeader(testHeader, LC_SYMTAB); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(result->cmd, LC_SYMTAB); +} + +- (void)testGetCommandByTypeFromHeader_NotFound +{ + // Create a test Mach-O header + mach_header_t header; + header.ncmds = 1; + + // Create a command + struct load_command cmd1; + cmd1.cmd = LC_SEGMENT_ARCH_DEPENDENT; + cmd1.cmdsize = sizeof(struct load_command); + + // Copy the command into the header memory + uint8_t buffer[sizeof(header) + sizeof(cmd1)]; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), &cmd1, sizeof(cmd1)); + + const mach_header_t* testHeader = (mach_header_t*)buffer; + + // Verify that the command is not found for a different type + const struct load_command* result = ksmacho_getCommandByTypeFromHeader(testHeader, LC_DYSYMTAB); + XCTAssertEqual(result, NULL); +} + +- (void)testGetCommandByTypeFromHeader_InvalidHeader +{ + // Test with an invalid header + const struct load_command* result = ksmacho_getCommandByTypeFromHeader(NULL, LC_SEGMENT_ARCH_DEPENDENT); + XCTAssertEqual(result, NULL); +} + +- (void)testGetSectionByTypeFlagFromSegment_NonLazySymbolPointers +{ + // Create a test segment + segment_command_t segment; + strcpy(segment.segname, "__DATA"); + segment.nsects = 1; + + // Create a section + section_t sect1; + strcpy(sect1.sectname, "__nl_symbol_ptr"); + sect1.flags = S_ATTR_PURE_INSTRUCTIONS | S_NON_LAZY_SYMBOL_POINTERS; + + // Copy the section into the segment memory + uint8_t buffer[sizeof(segment) + sizeof(sect1)]; + memcpy(buffer, &segment, sizeof(segment)); + memcpy(buffer + sizeof(segment), §1, sizeof(sect1)); + + const segment_command_t* testSegment = (segment_command_t*)buffer; + + // Verify that the section is found correctly + const section_t* result = ksmacho_getSectionByTypeFlagFromSegment(testSegment, S_NON_LAZY_SYMBOL_POINTERS); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(result->flags & SECTION_TYPE, S_NON_LAZY_SYMBOL_POINTERS); + XCTAssertEqual(strcmp(result->sectname, "__nl_symbol_ptr"), 0); +} + +- (void)testGetSectionByTypeFlagFromSegment_LazySymbolPointers +{ + // Create a test segment + segment_command_t segment; + strcpy(segment.segname, "__DATA"); + segment.nsects = 1; + + // Create a section + section_t sect2; + strcpy(sect2.sectname, "__la_symbol_ptr"); + sect2.flags = S_ATTR_SOME_INSTRUCTIONS | S_LAZY_SYMBOL_POINTERS; + + // Copy the section into the segment memory + uint8_t buffer[sizeof(segment) + sizeof(sect2)]; + memcpy(buffer, &segment, sizeof(segment)); + memcpy(buffer + sizeof(segment), §2, sizeof(sect2)); + + const segment_command_t* testSegment = (segment_command_t*)buffer; + + // Verify that the section is found correctly + const section_t* result = ksmacho_getSectionByTypeFlagFromSegment(testSegment, S_LAZY_SYMBOL_POINTERS); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(result->flags & SECTION_TYPE, S_LAZY_SYMBOL_POINTERS); + XCTAssertEqual(strcmp(result->sectname, "__la_symbol_ptr"), 0); +} + +- (void)testGetSectionByTypeFlagFromSegment_Regular +{ + // Create a test segment + segment_command_t segment; + strcpy(segment.segname, "__DATA"); + segment.nsects = 1; + + // Create a section + section_t sect3; + strcpy(sect3.sectname, "__const"); + sect3.flags = S_REGULAR; + + // Copy the section into the segment memory + uint8_t buffer[sizeof(segment) + sizeof(sect3)]; + memcpy(buffer, &segment, sizeof(segment)); + memcpy(buffer + sizeof(segment), §3, sizeof(sect3)); + + const segment_command_t* testSegment = (segment_command_t*)buffer; + + // Verify that the section is found correctly + const section_t* result = ksmacho_getSectionByTypeFlagFromSegment(testSegment, S_REGULAR); + XCTAssertNotEqual(result, NULL); + XCTAssertEqual(result->flags & SECTION_TYPE, S_REGULAR); + XCTAssertEqual(strcmp(result->sectname, "__const"), 0); +} + +- (void)testGetSectionByTypeFlagFromSegment_NotFound +{ + // Create a test segment + segment_command_t segment; + strcpy(segment.segname, "__DATA"); + segment.nsects = 1; + + // Create a section + section_t sect1; + strcpy(sect1.sectname, "__nl_symbol_ptr"); + sect1.flags = S_ATTR_PURE_INSTRUCTIONS | S_NON_LAZY_SYMBOL_POINTERS; + + // Copy the section into the segment memory + uint8_t buffer[sizeof(segment) + sizeof(sect1)]; + memcpy(buffer, &segment, sizeof(segment)); + memcpy(buffer + sizeof(segment), §1, sizeof(sect1)); + + const segment_command_t* testSegment = (segment_command_t*)buffer; + + // Verify that the section is not found for a different type flag + const section_t* result = ksmacho_getSectionByTypeFlagFromSegment(testSegment, S_ATTR_DEBUG); + XCTAssertEqual(result, NULL); +} + +- (void)testGetSectionByTypeFlagFromSegment_InvalidSegment +{ + // Test with an invalid segment + const section_t* result = ksmacho_getSectionByTypeFlagFromSegment(NULL, S_NON_LAZY_SYMBOL_POINTERS); + XCTAssertEqual(result, NULL); +} + +- (void)testGetSectionProtection_ReadOnlyProtection +{ + // Create a memory region with read-only protection + vm_address_t address; + vm_size_t size = getpagesize(); + vm_prot_t expectedProtection = VM_PROT_READ; + kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE); + XCTAssertEqual(result, KERN_SUCCESS); + + result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection); + XCTAssertEqual(result, KERN_SUCCESS); + + // Call the function under test + vm_prot_t actualProtection = ksmacho_getSectionProtection((void*)address); + + // Verify the expected protection + XCTAssertEqual(actualProtection, expectedProtection); + + // Deallocate the memory region + result = vm_deallocate(mach_task_self(), address, size); + XCTAssertEqual(result, KERN_SUCCESS); +} + +- (void)testGetSectionProtection_ExecutableProtection +{ + // Create a memory region with executable protection + vm_address_t address; + vm_size_t size = getpagesize(); + vm_prot_t expectedProtection = VM_PROT_READ | VM_PROT_EXECUTE; + kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE); + XCTAssertEqual(result, KERN_SUCCESS); + + result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection); + XCTAssertEqual(result, KERN_SUCCESS); + + // Call the function under test + vm_prot_t actualProtection = ksmacho_getSectionProtection((void*)address); + + // Verify the expected protection + XCTAssertEqual(actualProtection, expectedProtection); + + // Deallocate the memory region + result = vm_deallocate(mach_task_self(), address, size); + XCTAssertEqual(result, KERN_SUCCESS); +} + +- (void)testGetSectionProtection_NoAccessProtection +{ + // Create a memory region with no access protection + vm_address_t address; + vm_size_t size = getpagesize(); + vm_prot_t expectedProtection = VM_PROT_NONE; + kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE); + XCTAssertEqual(result, KERN_SUCCESS); + + result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection); + XCTAssertEqual(result, KERN_SUCCESS); + + // Call the function under test + vm_prot_t actualProtection = ksmacho_getSectionProtection((void*)address); + + // Verify the expected protection + XCTAssertEqual(actualProtection, expectedProtection); + + // Deallocate the memory region + result = vm_deallocate(mach_task_self(), address, size); + XCTAssertEqual(result, KERN_SUCCESS); +} + +- (void)testGetSectionProtection_FailureScenario +{ + // Call the function under test with an invalid memory address + vm_address_t invalidAddress = 0xFFFFFFFFFFFFFFFFULL; + vm_prot_t actualProtection = ksmacho_getSectionProtection((void*)invalidAddress); + + // Verify the expected default protection value + XCTAssertEqual(actualProtection, VM_PROT_READ, @"Expected default protection value of VM_PROT_READ"); +} + +@end