facebook / FBRetainCycleDetector

iOS library to help detecting retain cycles in runtime.
Other
4.22k stars 592 forks source link

iOS15 fishhook crash #122

Open okayz opened 3 years ago

okayz commented 3 years ago

image

KennyHito commented 3 years ago

我也有这个问题,临时解决办法,把这行注释掉,但是有啥影响不知道

xiaogehenjimo commented 3 years ago

replace indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
with if (i < (sizeof(indirect_symbol_bindings) / sizeof(indirect_symbol_bindings[0]))){ indirect_symbol_bindings[i] = cur->rebindings[j].replacement; }

tigerAndBull commented 3 years ago

replace in podfile, no need to rely on source code

` def replaceFishhookFile(fileName) File.chmod(0777, fileName) File.open(fileName, "w") do |f| f.write "// Copyright (c) 2013, Facebook, Inc.\n// All rights reserved.\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n// Redistributions of source code must retain the above copyright notice,\n// this list of conditions and the following disclaimer.\n// Redistributions in binary form must reproduce the above copyright notice,\n// this list of conditions and the following disclaimer in the documentation\n// and/or other materials provided with the distribution.\n// Neither the name Facebook nor the names of its contributors may be used to\n// endorse or promote products derived from this software without specific\n// prior written permission.\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\n// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include \"fishhook.h\"\n\n#include \n#include \n#include \n#include \n#include <sys/mman.h>\n#include <sys/types.h>\n#include <mach/mach.h>\n#include <mach/vm_map.h>\n#include <mach/vm_region.h>\n#include <mach-o/dyld.h>\n#include <mach-o/loader.h>\n#include <mach-o/nlist.h>\n\n#ifdef LP64\ntypedef struct mach_header_64 mach_header_t;\ntypedef struct segment_command_64 segment_command_t;\ntypedef struct section_64 section_t;\ntypedef struct nlist_64 nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64\n#else\ntypedef struct mach_header mach_header_t;\ntypedef struct segment_command segment_command_t;\ntypedef struct section section_t;\ntypedef struct nlist nlist_t;\n#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT\n#endif\n\n#ifndef SEG_DATA_CONST\n#define SEG_DATA_CONST \"__DATA_CONST\"\n#endif\n\nstruct rebindings_entry {\n struct rcd_rebinding rebindings;\n size_t rebindings_nel;\n struct rebindings_entry next;\n};\n\nstatic struct rebindings_entry _rebindings_head;\n\nstatic int prepend_rebindings(struct rebindings_entry rebindings_head,\n struct rcd_rebinding rebindings[],\n size_t nel) {\n struct rebindings_entry new_entry = (struct rebindings_entry ) malloc(sizeof(struct rebindings_entry));\n if (!new_entry) {\n return -1;\n }\n new_entry->rebindings = (struct rebinding ) malloc(sizeof(struct rcd_rebinding) nel);\n if (!new_entry->rebindings) {\n free(new_entry);\n return -1;\n }\n memcpy(new_entry->rebindings, rebindings, sizeof(struct rcd_rebinding) nel);\n new_entry->rebindings_nel = nel;\n new_entry->next = rebindings_head;\n rebindings_head = new_entry;\n return 0;\n}\n\n#if 0\nstatic int get_protection(void addr, vm_prot_t prot, vm_prot_t max_prot) {\n mach_port_t task = mach_task_self();\n vm_size_t size = 0;\n vm_address_t address = (vm_address_t)addr;\n memory_object_name_t object;\n#ifdef LP64\n mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;\n vm_region_basic_info_data_64_t info;\n kern_return_t info_ret = vm_region_64(\n task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object);\n#else\n mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;\n vm_region_basic_info_data_t info;\n kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object);\n#endif\n if (info_ret == KERN_SUCCESS) {\n if (prot != NULL)\n prot = info.protection;\n\n if (max_prot != NULL)\n max_prot = info.max_protection;\n\n return 0;\n }\n\n return -1;\n}\n#endif\n\nstatic void perform_rebinding_with_section(struct rebindings_entry rebindings,\n section_t section,\n intptr_t slide,\n nlist_t symtab,\n char strtab,\n uint32_t indirect_symtab) {\n uint32_t indirect_symbol_indices = indirect_symtab + section->reserved1;\n void indirect_symbol_bindings = (void )((uintptr_t)slide + section->addr);\n\n for (uint i = 0; i < section->size / sizeof(void ); i++) {\n uint32_t symtab_index = indirect_symbol_indices[i];\n if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||\n symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {\n continue;\n }\n uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;\n char symbol_name = strtab + strtab_offset;\n bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];\n struct rebindings_entry cur = rebindings;\n while (cur) {\n for (uint j = 0; j < cur->rebindings_nel; j++) {\n if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {\n kern_return_t err;\n\n if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement)\n (cur->rebindings[j].replaced) = indirect_symbol_bindings[i];\n\n /\n 1. Moved the vm protection modifying codes to here to reduce the\n changing scope.\n 2. Adding VM_PROT_WRITE mode unconditionally because vm_region\n API on some iOS/Mac reports mismatch vm protection attributes.\n -- Lianfu Hao Jun 16th, 2021\n /\n err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);\n if (err == KERN_SUCCESS) {\n /\n Once we failed to change the vm protection, we\n MUST NOT continue the following write actions!\n iOS 15 has corrected the const segments prot.\n * -- Lionfore Hao Jun 11th, 2021\n */\n indirect_symbol_bindings[i] = cur->rebindings[j].replacement;\n }\n goto symbol_loop;\n }\n }\n cur = cur->next;\n }\n symbol_loop:;\n }\n}\n\nstatic void rebind_symbols_for_image(struct rebindings_entry rebindings,\n const struct mach_header header,\n intptr_t slide) {\n Dl_info info;\n if (dladdr(header, &info) == 0) {\n return;\n }\n\n segment_command_t cur_seg_cmd;\n segment_command_t linkedit_segment = NULL;\n struct symtab_command symtab_cmd = NULL;\n struct dysymtab_command dysymtab_cmd = NULL;\n\n uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);\n for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {\n cur_seg_cmd = (segment_command_t )cur;\n if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {\n linkedit_segment = cur_seg_cmd;\n }\n } else if (cur_seg_cmd->cmd == LC_SYMTAB) {\n symtab_cmd = (struct symtab_command)cur_seg_cmd;\n } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {\n dysymtab_cmd = (struct dysymtab_command)cur_seg_cmd;\n }\n }\n\n if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||\n !dysymtab_cmd->nindirectsyms) {\n return;\n }\n\n // Find base symbol/string table addresses\n uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;\n nlist_t symtab = (nlist_t )(linkedit_base + symtab_cmd->symoff);\n char strtab = (char )(linkedit_base + symtab_cmd->stroff);\n\n // Get indirect symbol table (array of uint32_t indices into symbol table)\n uint32_t indirect_symtab = (uint32_t )(linkedit_base + dysymtab_cmd->indirectsymoff);\n\n cur = (uintptr_t)header + sizeof(mach_header_t);\n for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {\n cur_seg_cmd = (segment_command_t )cur;\n if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {\n if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&\n strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {\n continue;\n }\n for (uint j = 0; j < cur_seg_cmd->nsects; j++) {\n section_t sect =\n (section_t )(cur + sizeof(segment_command_t)) + j;\n if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {\n perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);\n }\n if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {\n perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);\n }\n }\n }\n }\n}\n\nstatic void _rebind_symbols_for_image(const struct mach_header header,\n intptr_t slide) {\n rebind_symbols_for_image(_rebindings_head, header, slide);\n}\n\nint rcd_rebind_symbols_image(void header,\n intptr_t slide,\n struct rcd_rebinding rebindings[],\n size_t rebindings_nel) {\n struct rebindings_entry rebindings_head = NULL;\n int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);\n rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);\n if (rebindings_head) {\n free(rebindings_head->rebindings);\n }\n free(rebindings_head);\n return retval;\n}\n\nint rcd_rebind_symbols(struct rcd_rebinding rebindings[], size_t rebindings_nel) {\n int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);\n if (retval < 0) {\n return retval;\n }\n // If this was the first call, register callback for image additions (which is also invoked for\n // existing images, otherwise, just run on existing images\n if (!_rebindings_head->next) {\n _dyld_register_func_for_add_image(_rebind_symbols_for_image);\n } else {\n uint32_t c = _dyld_image_count();\n for (uint32_t i = 0; i < c; i++) {\n _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));\n }\n }\n return retval;\n}\n" end end

def FishhookFormatError replaceFishhookFile("Pods/FBRetainCycleDetector/fishhook/fishhook.c") end

post_install do |installer| installer.pods_project.targets.each do |target| FishhookFormatError() end end `

ericXjj commented 3 years ago

fixed the crash in podfile:

`post_install do |installer| find_and_replace("Pods/FBRetainCycleDetector/fishhook/fishhook.c", "indirect_symbol_bindings[i] = cur->rebindings[j].replacement;", "if (i < (sizeof(indirect_symbol_bindings) / sizeof(indirect_symbol_bindings[0]))) { \n indirect_symbol_bindings[i]=cur->rebindings[j].replacement; \n }") end end

def find_and_replace(dir, findstr, replacestr) Dir[dir].each do |name| text = File.read(name) replace = text.gsub(findstr,replacestr) if text != replace puts "Fix: " + name File.open(name, "w") { |file| file.puts replace } STDOUT.flush end end Dir[dir + '*/'].each(&method(:find_and_replace)) end`

zhongTom commented 3 years ago

I have the same question

qiming-guo commented 3 years ago

fixed the crash in podfile:

`post_install do |installer| find_and_replace("Pods/FBRetainCycleDetector/fishhook/fishhook.c", "indirect_symbol_bindings[i] = cur->rebindings[j].replacement;", "if (i < (sizeof(indirect_symbol_bindings) / sizeof(indirect_symbol_bindings[0]))) { \n indirect_symbol_bindings[i]=cur->rebindings[j].replacement; \n }") end end

def find_and_replace(dir, findstr, replacestr) Dir[dir].each do |name| text = File.read(name) replace = text.gsub(findstr,replacestr) if text != replace puts "Fix: " + name File.open(name, "w") { |file| file.puts replace } STDOUT.flush end end Dir[dir + '*/'].each(&method(:find_and_replace)) end`

It works well! ThankU

dmaclach commented 3 years ago

This fix is not valid AFAICT see https://github.com/facebook/FBRetainCycleDetector/pull/123#discussion_r746256157