Highlight
macOS Sonoma 引入 Environment Constraints,允许开发者在二进制文件中嵌入启动约束和库加载约束,精确控制哪些进程可以启动自己的辅助工具、XPC 服务,以及哪些代码可以加载到进程地址空间。
核心内容
为什么需要环境约束
功能丰富的 Mac 应用通常包含多个组件:框架、辅助工具、XPC 服务、启动代理等。这些组件运行在潜在敌对的环境中,面临多种安全威胁:
- 恶意进程可能通过
posix_spawn控制子进程的输入和资源访问 - 攻击者可能修改磁盘文件,向受害进程注入意外数据
- 辅助工具被非法调用时可能泄露钥匙串或 iCloud 数据
(00:48)
现有的安全机制(App Sandbox、Hardened Runtime、Gatekeeper)主要关注运行中的进程本身,而非进程的执行环境。环境约束填补了这一空白。
环境约束的三种类型
启动约束(Launch Constraints) 嵌入到特定二进制文件中,定义三类属性:
- Self Constraints:进程自身的属性要求
- Parent Process Constraints:哪些父进程可以启动它
- 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-validationentitlement,避免”允许任何人”的安全降级
示例:父进程约束
限制辅助工具只能由主应用启动:
<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 指向的可执行文件
- 通过
SMAppServiceAPI 注册 plist 时生效
可用性
- 库加载约束和 launchd plist 约束:目标平台任意 macOS 版本, enforcement 从 macOS Sonoma 开始
- 启动约束:目标平台 macOS 13.3+,enforcement 从 macOS 13.3 开始
(15:51)
核心启发
-
为辅助工具添加父进程约束
- 做什么:限制你的命令行辅助工具只能被主应用启动
- 为什么值得做:防止攻击者直接调用辅助工具访问钥匙串或 iCloud 数据
- 怎么开始:创建一个约束 plist 文件,在 Xcode 的 “Launch Constraint Parent Process Plist” 中引用
-
为 XPC 服务添加负责进程约束
- 做什么:限制哪些进程可以触发你的 XPC 服务
- 为什么值得做:防止 XPC 服务被提取后独立调用,保护分配给它的特权
- 怎么开始:在 “Launch Constraint Responsible Process Plist” 中配置允许的签名标识符列表
-
用库加载约束替代 disable-library-validation
- 做什么:精确控制允许加载的第三方库,而非完全关闭库验证
- 为什么值得做:在集成第三方库的同时,防止任意签名代码注入
- 怎么开始:创建包含
$in操作符的约束 plist,列出允许的团队标识符
-
为启动代理添加 SpawnConstraint
- 做什么:在 launchd plist 中嵌入启动约束
- 为什么值得做:确保用户批准的后台任务不会被篡改指向恶意代码
- 怎么开始:在 plist 中添加
SpawnConstraint字典,通过SMAppService注册
关联 Session
- What’s new in privacy — 了解 Apple 平台最新的隐私保护机制
- Verify app dependencies with digital signatures — 使用数字签名验证应用依赖
- Deploy passkeys in your enterprise — 在企业环境中部署 Passkey
- Security Overview — Apple 安全架构概览
评论
GitHub Issues · utterances