[macOS翻译]解决库加载问题

620 阅读8分钟

本文由 简悦 SimpRead转码, 原文地址 developer.apple.com

本帖是与可信执行系统相关的帖子集的一部分。如果您发现自己的 w......

本帖是与可信执行系统相关的帖子集的一部分。如果您是直接找到这里的,我建议您 从顶部开始

Resolving Library Loading Problems

在 macOS 上,动态链接器负责加载进程使用的动态链接库。这分为两个部分:

  • 当进程运行可执行文件时,动态链接器会加载该可执行文件使用的所有库,以及这些库使用的库,等等。

  • 进程可以使用 dlopenNSBundle 等应用程序接口在运行时加载库。有关 dlopen 及其朋友的信息,请参阅 dlopen man page

动态链接器与可信执行系统密切合作,确保只加载适当的库。动态链接库冒充攻击是一个重要问题。如果你的程序引用了某个库,你希望动态链接器加载的是该库的副本,而不是攻击者安装的其他副本。

主要的保护措施是库验证。如果在可执行文件上启用了库验证,则可信执行系统只允许进程加载经 Apple 签名或与可执行文件具有相同团队 ID 的代码。

库验证由 Hardened Runtime 启用,但您可以使用 Disable Library Validation Entitlement (com.apple.security.cs.disable-library-validation)权限退出库验证。

重要 请启用库验证。只有当您的应用程序需要加载其他第三方开发人员提供的插件时,才能禁用它。禁用库验证会增加通过 Gatekeeper 的难度。有关详情,请参阅 解决加载命令路径混乱导致的 Gatekeeper 问题

当动态链接器加载库失败时,它会在崩溃报告中给出解释。例如

Termination Reason:    Namespace DYLD, Code 1 Library missing
Library not loaded: @rpath/libEtranger.dylib
Referenced from: /Users/USER/*/LinkToEtranger.app/Contents/MacOS/LinkToEtranger
Reason: 
(terminated at launch; ignore backtrace)

Application Specific Information:
Library not loaded: @rpath/libEtranger.dylib
Referenced from: 
Reason: 

这一解释可能会被崩溃报告系统截断。要查看完整日志,请从终端运行应用程序:

% ./LinkToEtranger.app/Contents/MacOS/LinkToEtranger 
dyld[79650]: Library not loaded: @rpath/libEtranger.dylib
  Referenced from: …/LinkToEtranger.app/Contents/MacOS/LinkToEtranger
  Reason: tried: '…/LinkToEtranger.app/Contents/MacOS/../Frameworks/libEtr
    anger.dylib' (code signature in <E16EDD14-CE5A-33BC-9B06-554A3BC12C51> 
    '…/LinkToEtranger.app/Contents/Frameworks/libEtranger.dylib' not valid 
    for use in process: mapping process and mapped file (non-platform) have 
    different Team IDs), '…/LinkToEtranger.app/Contents/MacOS/../
    Frameworks/libEtranger.dylib' (code signature in <E16EDD14-CE5A-33BC-
    9B06-554A3BC12C51> '…/LinkToEtranger.app/Contents/Frameworks/
    libEtranger.dylib' not valid for use in process: mapping process and 
    mapped file (non-platform) have different Team IDs), '/usr/local/lib/
    libEtranger.dylib' (no such file), '/usr/lib/libEtranger.dylib' (no 
    such file)
zsh: abort      ./LinkToEtranger.app/Contents/MacOS/LinkToEtranger

"Reason "行超长,请尝试将其分开:

'…/LinkToEtranger.app/Contents/MacOS/../Frameworks/libEtranger.dylib' 
    (…), 
'/usr/local/lib/libEtranger.dylib' (no such file), 
'/usr/lib/libEtranger.dylib' (no such file)

每个条目都以动态链接器试图查找库的位置开头,然后在括号内加上文本,如 "无此类文件",解释出错的原因。

注意 这些信息的确切格式因 macOS 的不同版本而异。

其中许多原因与可信执行系统无关。例如,"无此类文件 "表示磁盘上没有该库。不过,有三个常见的可信执行问题:

  • 库验证

  • 使用旧的 macOS SDK

  • 库代码的权限受限

有关动态链接器的更多信息,请参阅 dyld man page。具体来说,"DYLD_PRINT_SEARCHING "环境变量在调试库加载问题时非常有用。

Library Validation

在任何实际情况下,动态链接器的 Reason 输出都超长。为了更好地理解它,请尝试将其拆分:

'…/LinkToEtranger.app/Contents/MacOS/../Frameworks/libEtranger.dylib' 
    (code signature in <E16EDD14-CE5A-33BC-9B06-554A3BC12C51> 
    '…/LinkToEtranger.app/Contents/Frameworks/libEtranger.dylib' 
    not valid for use in process: mapping process and mapped file 
    (non-platform) have different Team IDs), 
'/usr/local/lib/libEtranger.dylib' (no such file), 
'/usr/lib/libEtranger.dylib' (no such file)

动态链接器查找了三个不同的地方:

  • 应用程序的 "Frameworks "目录

  • /usr/local/lib 目录

  • /usr/lib 目录

第一个地方很重要,因为它的路径与预期的库位置一致。动态链接器记录了对问题的精彩解释:

code signature in'…/LinkToEtranger.app/Contents/Frameworks/
libEtranger.dylib' not valid for use in process: mapping process 
and mapped file (non-platform) have different Team IDs

总之,动态链接器没有加载这个libEtranger.dylib副本,因为它不是系统库(非平台),而且它的 Team ID 与进程的主可执行文件不同。到 codesign 快速查看一下就能证实这一点:

% codesign -d -vvv LinkToEtranger.app 
…
TeamIdentifier=SKMME9E2Y8
…
% codesign -d -vvv LinkToEtranger.app/Contents/Frameworks/libEtranger.dylib 
…
TeamIdentifier=VL9SQP756U
…

如何解决这个问题取决于产品的性质。如果该库是作为产品的一部分安装的,请使用与您的团队 ID 相关联的签名标识重新签署该库。即使您没有亲自构建代码,也要这样做。毕竟,你负责将库安装到用户的机器上,其签名应反映这一点。

还有一种可能是,你正在构建一个支持插件的程序,因此需要加载由其他第三方开发者签名的插件。在这种情况下,解决方法是使用 Disable Library Validation Entitlement 权限 (com.apple.security.cs.disable-library-validation)签署可执行文件,从而禁用库验证。

重要 禁用库验证会增加通过 Gatekeeper 的难度。有关详情,请参阅 解决由悬垂加载命令路径引起的 Gatekeeper 问题

Use of an Old macOS SDK

另一个与可信执行系统有关的动态库加载故障如下所示:

code signature in … '…/LinkToDodo.app/Contents/Frameworks/libDodo.dylib' 
not valid for use in process: mapped file has no cdhash, completely 
unsigned? Code has to be at least ad-hoc signed.

注意 此信息中的 "cdhash "指的是代码目录哈希值。有关 cdhashes 的更多信息,请参阅 TN3126 Inside Code Signing: Hashes

这就更难理解了,尤其是因为该库实际上 签名的:

% codesign -d -vvv LinkToDodo.app/Contents/Frameworks/libDodo.dylib 
…
Authority=Apple Development: …
…

Notarizing macOS Software Before Distribution中可以找到解释:

苹果的公证服务要求你采取以下保护措施:

...

  • 与 macOS 10.9 或更高版本的 SDK 相链接

macOS 10.9 引入了重要的代码签名改进。加固运行时依赖于这些改进。它会通过查看代码所使用的 SDK 来确认这些改进是否存在。如果代码是使用旧版 SDK 构建的,或没有使用 SDK 的记录,加固运行时就会拒绝加载代码。

在本例中,LinkToDodo 应用程序与现代 SDK 进行了链接,但libDodo.dylib中却没有与之构建的 SDK 的记录:

% vtool -show-build LinkToDodo.app/Contents/MacOS/LinkToDodo 
…
      cmd LC_BUILD_VERSION
…
      sdk 12.3
…
% vtool -show-build LinkToDodo.app/Contents/Frameworks/libDodo.dylib 
LinkToDodo.app/Contents/Frameworks/libDodo.dylib:
% 

这就是错误的原因:

  1. 进程已启用加固运行时。

  2. 加固运行时要求所有代码都使用 macOS 10.9 SDK 或更高版本构建。

  3. libDodo.dylib 没有使用 SDK 构建的记录,因此可信执行系统阻止加载它。

  4. 动态链接器在解释问题时会报告这一点。

最好的解决办法是使用最新工具从源代码中重建代码。如果你现在无法这样做,请参阅 Notarisation and the macOS 10.9 SDK 了解解决方法。

重要 这是一项短期兼容性措施。计划尽快从源代码中重建此代码。如果你从其他第三方开发者那里获得了代码,请确保他们知道这个问题。

最后,如果您只能在现场重现此问题,并设法获取了系统诊断日志,请在系统日志中查找类似以下的日志条目:

type: default
time: 2022-05-20 13:12:11.185889 +0100
process: kernel
category: <Missing Description>
message: …/LinkToDodo.app/Contents/Frameworks/libDodo.dylib: Possible race detected. Rejecting.

这是一把隐秘的 "烟枪"!

有关系统日志的一般信息,请参阅 Your Friend the System Log

第三个与可信执行系统有关的动态链接库加载失败情况如下:


OS Version:            macOS 11.6.5 (20G527)


Termination Reason:    DYLD, [0x5] Code Signature

Application Specific Information:
dyld: launch, loading dependent libraries

Dyld Error Message:
  Library not loaded: @rpath/OverlyEntitled.framework/Versions/A/OverlyEntitled
  Referenced from: /Users/USER/AppWithEntitlementLibrary.app/Contents/MacOS/AppWithEntitlementLibrary
  Reason: no suitable image found.  Did find:
	…/AppWithEntitlementLibrary.app/Contents/MacOS/../Frameworks/OverlyEntitled.framework/Versions/A/OverlyEntitled: code signature invalid for '…/AppWithEntitlementLibrary.app/Contents/MacOS/../Frameworks/OverlyEntitled.framework/Versions/A/OverlyEntitled'

注意 本崩溃报告来自 macOS 11。由于......嗯......原因......macOS 12 忽略了库代码的权限。不过,这在 macOS 13 中又发生了变化,其失败方式与 macOS 11 大同小异。

不过,代码签名是有效的:

% codesign -v -vvv AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework
AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework: valid on disk
AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework: satisfies its Designated Requirement

它还通过了上一节概述的两项测试:

% codesign -d -vvv AppWithEntitlementLibrary.app
…
TeamIdentifier=SKMME9E2Y8
…
% codesign -d -vvv AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework 
…
TeamIdentifier=SKMME9E2Y8
…
% vtool -show-build AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework/Versions/A/OverlyEntitled 
…
      sdk 12.3
…

问题在于框架签署了受限权限:

% codesign -d --entitlements - AppWithEntitlementLibrary.app/Contents/Frameworks/OverlyEntitled.framework 
…
[Dict]
  [Key] com.apple.developer.networking.vpn.api
  [Value]
    [Array]
      [String] allow-vpn

权限只有应用于主可执行文件时才有效。非主可执行文件的代码称为 库代码 ,包括框架、动态库和捆绑包。不要对库代码应用权限。充其量这只是善意的。更糟的是,它会导致代码签名崩溃,就像下面这样。

有关主可执行文件的详细信息,请参阅为 Mac 创建发行版签名代码

TN3125 Inside Code Signing: Provisioning Profiles 中的 Entitlements on macOS 部分定义了受限权限,并明确指出在 macOS 上,可执行文件要求的每个受限权限都必须获得其供应配置文件的授权。不过,库代码没有嵌入式供应配置文件:

  • 共享库没有捆绑结构,因此 不能 包含供应配置文件。

  • 有捆绑结构的库代码、框架和捆绑包 可以 有供应配置文件,但包括 Xcode 在内的大多数工具都不嵌入供应配置文件。

因此,OverlyEntitled 框架声称拥有受限权限,但该声称并未获得配置文件的授权,因此可信执行系统会告诉动态链接器不要加载它。

要解决这个问题,可以更改代码签名设置,使只有主可执行文件才可申请权限。有关该主题的详细建议,请参阅 Creating Distribution-Signed Code for Mac

重要 导致此问题的首要原因是用户使用 --deep 签署代码。出于这个原因以及--deep被认为有害中概述的其他原因,请勿这样做。

2022-12-13 更新了 Restricted Entitlements on Library Code 部分的说明,以考虑 macOS 13 "回到度量标准"。

2022-09-26 修复了一个断开的链接。

2022-06-13 添加了 "库代码中的受限权限 "部分。

2022-05-20 首次发布。