前因
App 接入了动态库后,iOS 9.x 版本启动 Crash,报错如下:
dyld: warning, LC_RPATH @excutable_path/Frameworks in ... being ignored in restricted program because of @excutable_path/
dyld: warning, LC_RPATH @loader_path/Frameworks in ... being ignored in restricted program because of @loader_path/
dyld: Library not loaded : @rapth/...
Referenced from:
...
Reason: image not found
为什么 iOS 9.x 会 Crash ?
由报错可知,dyld 未加载到对应动态库导致崩溃,通过对比不同版本手机的崩溃栈信息以及提示,发现dyld栈信息不同并且其他版本无dyld:warning,推测由于 dyld 版本不同导致。前往 dyld 源码查找 iOS 9.x 大致版本为 360.22 并下载。
通过 Crash 的栈信息,可知执行顺序为 _dyld_start
-> dyldbootstrap::start()
-> dyld::main()
-> dyld::setContext()
-> dyld::checkEnvironmentVariables()
-> dyld::checkLoadCommandEnvironmentVariables()
-> ...-> dyld::load()
-> dyld::link()
dyld::link
在工程中搜索 error 关键字,通过下列信息找到 dyld::link()
调用时异常,通过函数名可确认为链接动态库时失败
-
搜索
Library not loaded:
,只在ImageLoader::recursiveLoadLibraries
有报错void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths) { //... const char* newMsg = dyld::mkstringf("Library not loaded: %s\n Referenced from: %s\n Reason: %s", //..
-
搜索
recursiveLoadLibraries(
, 只在ImageLoader::link
中有调用 -
搜索
link(
, 只在dyld::link(
中有调用
getRPaths
由于控制台 dyld:warning ,推测是因为路径被忽略导致的动态库无法链接
-
搜索
being ignored in restricted program
void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector<const char*>& paths) const { //.. if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { if ( context.processIsRestricted && !context.processRequiresLibraryValidation && (context.mainExecutable == this) ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath()); break; } //... } else if ( (strncmp(path, "@executable_path", 16) == 0) && ((path[16] == '/') || (path[16] == '\0')) ) { if ( context.processIsRestricted && !context.processRequiresLibraryValidation ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @executable_path\n", path, this->getPath()); break; } //... } else if ( (path[0] != '/') && context.processIsRestricted && !context.processRequiresLibraryValidation ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because it is a relative path\n", path, this->getPath()); break; } //... }
在上面的代码中,发现警告出现的情况为 context.processIsRestricted && !context.processRequiresLibraryValidation
processIsRestricted
-
在工程中搜索
processIsRestricted
,发现其实是sProcessIsRestricted
,值初始化为false
bool processIsRestricted() { return sProcessIsRestricted; }
-
processRestricted()
函数返回值会赋值给sProcessIsRestricted
sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
-
查看
processRestricted
中的注释,发现Respect __RESTRICT,__restrict section for root processes
static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation) { #if TARGET_IPHONE_SIMULATOR gLinkContext.codeSigningEnforced = true; #else // ask kernel if code signature of program makes it restricted uint32_t flags; if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { if (flags & CS_REQUIRE_LV) *processRraryValequiresLibidation = true; #if __MAC_OS_X_VERSION_MIN_REQUIRED if ( flags & CS_ENFORCEMENT ) { gLinkContext.codeSigningEnforced = true; } if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) { sRestrictedReason = restrictedByEntitlements; return true; } #else if ((flags & CS_ENFORCEMENT) && !(flags & CS_GET_TASK_ALLOW)) { *ignoreEnvVars = true; } gLinkContext.codeSigningEnforced = true; #endif } #endif // all processes with setuid or setgid bit set are restricted if ( issetugid() ) { sRestrictedReason = restrictedBySetGUid; return true; } // <rdar://problem/13158444&13245742> Respect __RESTRICT,__restrict section for root processes if ( hasRestrictedSegment(mainExecutableMH) ) { // existence of __RESTRICT/__restrict section make process restricted sRestrictedReason = restrictedBySegment; return true; } return false; }
-
查看
hasRestrictedSegment
是去Mach-O
查找__RESTRICT segement
,通过 MachOView 打开工程可执行文件发现对应的 Segment,可知由于__RESTRICT
导致static bool hasRestrictedSegment(const macho_header* mh) { const uint32_t cmd_count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; //dyld::log("seg name: %s\n", seg->segname); if (strcmp(seg->segname, "__RESTRICT") == 0) { const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if (strcmp(sect->sectname, "__restrict") == 0) return true; } } } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } return false; }
__RESTRICT
在工程中搜索 RESTRICT
,发现 Other Linker Flags
中有这个字段 -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
,将其删除后,App运行成功成功。
为什么 iOS 10.0及以后正常呢?
-
下载 dyld 421.1 源码,发现
dyld::main
函数中多了宏判断__MAC_OS_X_VERSION_MIN_REQUIRED
,宏判断为 Mac 情况下调用pruneEnvironmentVariables()
函数#if __MAC_OS_X_VERSION_MIN_REQUIRED if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); } else #endif { checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); }
-
查看
pruneEnvironmentVariables()
函数,在此函数会移除全部DYLD_*
和LD_LIBRARY_PATH environment variables
,并且禁用disable framework and library fallback paths
static void pruneEnvironmentVariables(const char* envp[], const char*** applep) { // delete all DYLD_* and LD_LIBRARY_PATH environment variables int removedCount = 0; const char** d = envp; for(const char** s = envp; *s != NULL; s++) { if ( (strncmp(*s, "DYLD_", 5) != 0) && (strncmp(*s, "LD_LIBRARY_PATH=", 16) != 0) ) { *d++ = *s; } else { ++removedCount; } } *d++ = NULL; // <rdar://11894054> Disable warnings about DYLD_ env vars being ignored. The warnings are causing too much confusion. #if 0 if ( removedCount != 0 ) { dyld::log("dyld: DYLD_ environment variables being ignored because "); switch (sRestrictedReason) { case restrictedNot: break; case restrictedBySetGUid: dyld::log("main executable (%s) is setuid or setgid\n", sExecPath); break; case restrictedBySegment: dyld::log("main executable (%s) has __RESTRICT/__restrict section\n", sExecPath); break; case restrictedByEntitlements: dyld::log("main executable (%s) is code signed with entitlements\n", sExecPath); break; } } #endif // slide apple parameters if ( removedCount > 0 ) { *applep = d; do { *d = d[removedCount]; } while ( *d++ != NULL ); for(int i=0; i < removedCount; ++i) *d++ = NULL; } // disable framework and library fallback paths for setuid binaries rdar://problem/4589305 sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = NULL; sEnv.DYLD_FALLBACK_LIBRARY_PATH = NULL; if ( removedCount > 0 ) strlcat(sLoadingCrashMessage, ", ignoring DYLD_* env vars", sizeof(sLoadingCrashMessage)); }
总结
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
在 iOS 10 以前对于防护动态库注入有效。
网友评论