WWDC Quick Look 💓 By SwiftGGTeam
Protect your Mac app with environment constraints

Protect your Mac app with environment constraints

观看原视频

Highlight

macOS Sonoma 引入 Environment Constraints,允许开发者在二进制文件中嵌入启动约束和库加载约束,精确控制哪些进程可以启动自己的辅助工具、XPC 服务,以及哪些代码可以加载到进程地址空间。

核心内容

为什么需要环境约束

功能丰富的 Mac 应用通常包含多个组件:框架、辅助工具、XPC 服务、启动代理等。这些组件运行在潜在敌对的环境中,面临多种安全威胁:

  • 恶意进程可能通过 posix_spawn 控制子进程的输入和资源访问
  • 攻击者可能修改磁盘文件,向受害进程注入意外数据
  • 辅助工具被非法调用时可能泄露钥匙串或 iCloud 数据

00:48

现有的安全机制(App Sandbox、Hardened Runtime、Gatekeeper)主要关注运行中的进程本身,而非进程的执行环境。环境约束填补了这一空白。

环境约束的三种类型

启动约束(Launch Constraints) 嵌入到特定二进制文件中,定义三类属性:

  1. Self Constraints:进程自身的属性要求
  2. Parent Process Constraints:哪些父进程可以启动它
  3. Responsible Process Constraints:哪些进程可以对其负责

04:54

Launchd Plist 约束 通过 SpawnConstraint 键注册到启动代理/守护进程的 plist 中,确保只有符合约束的进程才能被 plist 启动。

库加载约束(Library Load Constraints) 控制哪些代码可以加载到进程地址空间,替代原有的 “全有或全无” 库验证方案。

实际场景

假设你的应用 MyDemo.app 包含以下组件:

  • 一个启动代理 DemoMenuBar.agent
  • 一个辅助工具 demohelper
  • 一个框架及其 XPC 服务
  • 一个第三方团队的库

10:21

如果没有约束,攻击者可以直接从终端运行 demohelper 并传入 --cloud 参数访问 iCloud 数据。设置父进程约束后,只有 MyDemo.app 能启动该辅助工具。

详细内容

约束的数据结构

环境约束使用 plist 字典编码,顶层键值对隐式进行逻辑 AND 运算。支持以下操作符:

  • $and / $or:逻辑与/或
  • $and-array / $or-array:限制字典嵌套层级
  • $in:指定允许的值的数组

07:55

常用事实键:

  • signing-identifier:代码签名标识符
  • team-identifier:开发团队标识符
  • cdhash:代码的唯一哈希值

示例:库加载约束

允许加载由自己团队或可信第三方团队签名的代码:

<dict>
    <key>team-identifier</key>
    <dict>
        <key>$in</key>
        <array>
            <string>M2657GZ2M9</string>
            <string>P9Z4AN7VHQ</string>
        </array>
    </dict>
</dict>

15:29

关键点:

  • team-identifier 指定允许的开发团队
  • $in 操作符接受字符串数组,允许多个团队
  • 此约束替代 disable-library-validation entitlement,避免”允许任何人”的安全降级

示例:父进程约束

限制辅助工具只能由主应用启动:

<dict>
    <key>team-identifier</key>
    <string>M2657GZ2M9</string>
    <key>signing-identifier</key>
    <string>com.demo.MyDemo</string>
</dict>

11:02

关键点:

  • 同时约束团队标识符和签名标识符,防止同团队其他应用冒用
  • 在 Xcode 的 “Launch Constraint Parent Process Plist” 设置中指定此文件
  • 违反约束时系统会生成崩溃报告

示例:进程启动约束

XPC 服务只允许特定进程访问:

<dict>
    <key>team-identifier</key>
    <string>M2657GZ2M9</string>
    <key>signing-identifier</key>
    <dict>
        <key>$in</key>
        <array>
            <string>com.demo.MyDemo</string>
            <string>com.demo.DemoMenuBar</string>
            <string>demohelper</string>
        </array>
    </dict>
</dict>

14:06

关键点:

  • 使用 $in 列出所有允许访问该 XPC 服务的进程签名标识符
  • 在 Xcode 的 “Launch Constraint Responsible Process Plist” 设置中配置
  • 防止 XPC 服务被从 bundle 中提取后独立调用

示例:Launchd Plist 约束

防止启动代理被篡改:

<dict>
    <key>Label</key>
    <string>com.demo.DemoMenuBar.agent</string>
    <key>BundleProgram</key>
    <string>Contents/Library/LaunchAgents/DemoMenuBar.app/Contents/MacOS/DemoMenuBar</string>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <true/>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>SpawnConstraint</key>
    <dict>
        <key>team-identifier</key>
        <string>M2657GZ2M9</string>
        <key>signing-identifier</key>
        <string>com.demo.DemoMenuBar</string>
    </dict>
</dict>

14:52

关键点:

  • SpawnConstraint 键定义 plist 允许启动的代码身份
  • 用户批准后台任务后,攻击者无法替换 plist 指向的可执行文件
  • 通过 SMAppService API 注册 plist 时生效

可用性

  • 库加载约束和 launchd plist 约束:目标平台任意 macOS 版本, enforcement 从 macOS Sonoma 开始
  • 启动约束:目标平台 macOS 13.3+,enforcement 从 macOS 13.3 开始

15:51

核心启发

  1. 为辅助工具添加父进程约束

    • 做什么:限制你的命令行辅助工具只能被主应用启动
    • 为什么值得做:防止攻击者直接调用辅助工具访问钥匙串或 iCloud 数据
    • 怎么开始:创建一个约束 plist 文件,在 Xcode 的 “Launch Constraint Parent Process Plist” 中引用
  2. 为 XPC 服务添加负责进程约束

    • 做什么:限制哪些进程可以触发你的 XPC 服务
    • 为什么值得做:防止 XPC 服务被提取后独立调用,保护分配给它的特权
    • 怎么开始:在 “Launch Constraint Responsible Process Plist” 中配置允许的签名标识符列表
  3. 用库加载约束替代 disable-library-validation

    • 做什么:精确控制允许加载的第三方库,而非完全关闭库验证
    • 为什么值得做:在集成第三方库的同时,防止任意签名代码注入
    • 怎么开始:创建包含 $in 操作符的约束 plist,列出允许的团队标识符
  4. 为启动代理添加 SpawnConstraint

    • 做什么:在 launchd plist 中嵌入启动约束
    • 为什么值得做:确保用户批准的后台任务不会被篡改指向恶意代码
    • 怎么开始:在 plist 中添加 SpawnConstraint 字典,通过 SMAppService 注册

关联 Session

评论

GitHub Issues · utterances