Hotdry.
systems

UEFI JavaScript 绑定的 ABI 设计与安全沙箱实现

分析 Promethee 项目的 UEFI JavaScript 绑定实现,聚焦 EFIAPI 调用约定的 ABI 对齐挑战,以及固件层 JS 运行时的安全沙箱工程化方案与可落地参数。

将 JavaScript 运行时嵌入 UEFI 固件层已不再是纯粹的科幻设想。近日出现的 Promethee 概念验证项目成功将 JS 解释器加载至引导环境,使固件级脚本化成为可能。然而,这一尝试触及了系统软件工程的核心难题:如何在保持 ABI 兼容性的同时,为解释型语言构建可信的隔离边界?本文从 ABI 设计挑战与安全沙箱实现两个维度展开分析,并提供可直接落地的工程参数。

ABI 设计挑战:从 EFIAPI 到 JS FFI 的桥接

UEFI 规范定义了严格的调用约定。在 x86_64 架构下,EFIAPI 映射至 Microsoft x64(Win64)ABI,这要求调用者遵循精确的寄存器分配规则:前四个整数或指针参数必须依次存入 RCX、RDX、R8、R9,浮点参数则占用 XMM0-XMM3;返回值通过 RAX(整型)或 XMM0(浮点)传递;栈必须保持 16 字节对齐,且调用者需在栈上预留 32 字节的阴影空间(shadow space)供被调用者使用。

对于 JavaScript 这类动态类型语言,FFI(Foreign Function Interface)层必须承担类型擦除与寄存器布局的双重转换任务。当 JS 代码调用 UEFI 协议(如 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL)时,绑定层需将 JS 值(Number、String、Object 引用)转换为符合 Win64 ABI 的 C 类型,并确保寄存器状态在边界穿越前后保持一致。由于 UEFI 固件在运行时服务阶段禁用中断,任何栈未对齐或寄存器污染都可能导致系统挂起或静默数据损坏。

进一步地,浮点单元在 UEFI 环境中历来是敏感区域。传统固件开发(如 GRUB 的 Lua 模块、Linux 内核)倾向于避免在固件层使用浮点运算,原因在于保存 / 恢复浮点上下文的开销及中断处理的复杂性。Promethee 项目声称支持浮点运算,这意味着其 ABI 桥接必须显式处理 XMM6-XMM15 非易失寄存器的保存,或在调用 UEFI 服务前切换到专用浮点状态帧。这一设计选择直接影响了解释器的中断延迟与上下文切换开销。

安全沙箱实现:威胁模型与隔离策略

将图灵完备的脚本引擎引入固件层不可避免地扩展了攻击面。威胁模型至少包含三类风险:解释器本身的内存安全漏洞(如 JIT 编译器的缓冲区溢出)、恶意 JS 代码对 UEFI 运行时服务的滥用(如直接操作 NVRAM 变量或绕过 Secure Boot 验证),以及供应链污染(通过未签名的脚本注入持久化后门)。

针对这些风险,沙箱实现需采用分层防御策略:

API 白名单与能力模型。不应将完整的 UEFI 协议表暴露给 JS 运行时。建议实施最小权限原则,仅开放经过审计的协议子集(如基础文本输出、内存分配服务),并通过能力令牌(capability token)进行访问控制。例如,网络协议(EFI_NETWORK_INTERFACE_IDENTIFIER)应默认禁用,仅在显式授权后通过 IPC 句柄暴露。

内存隔离与执行边界。固件层缺乏操作系统级的虚拟内存保护,但可利用 UEFI 的内存映射服务创建独立的 EFI_BOOT_SERVICES 内存池,将 JS 解释器的堆空间与核心固件数据隔离。建议配置池大小上限(如 4MB),并启用写入时复制(Copy-on-Write)监控。对于关键操作,可借鉴 SMM(System Management Mode)的隔离思想,将敏感 UEFI 调用委托至受保护的执行上下文,尽管这会增加 SMI(System Management Interrupt)的触发频率。

代码完整性与验证链。所有 JS 代码在加载前必须通过 Authenticode 签名验证,且哈希值应记录在 TPM 的平台配置寄存器(PCR)中。建议实施严格的 CSP(Content Security Policy):禁止动态代码执行(evalFunction 构造器)、限制网络请求至预定义的 HTTPS 端点,并对全局对象进行深度冻结(Object.freeze)以防止原型污染攻击。

可落地的工程参数清单

基于上述分析,以下参数可直接应用于 UEFI JavaScript 绑定的生产环境:

ABI 对齐检查项

  • 启用编译器的 ms_abi 属性(GCC/Clang:-mabi=ms),确保 UEFI 入口点使用正确的调用约定。
  • 在 FFI 边界插入 static_assert 验证结构体大小与对齐(如 sizeof(EFI_STATUS) == 8)。
  • 对浮点调用路径实施上下文保存测试,验证 XMM6-XMM15 在跨边界调用前后的值一致性。

沙箱策略配置

  • JS 堆内存上限:4MB(可配置为 1MB-16MB 范围,根据固件存储容量调整)。
  • 执行超时阈值:30 秒(防止无限循环阻塞引导流程)。
  • API 调用速率限制:每 100ms 内最多 1000 次协议调用(缓解 DoS 攻击)。
  • 禁用 Date.now()Math.random() 的高精度实现,以防止侧信道计时攻击。

安全测试点

  • 模糊测试(Fuzzing)FFI 边界,特别是变长参数列表(如 Print 函数)的格式字符串处理。
  • 验证 Secure Boot 链:确保加载的 JS 字节码哈希与签名证书匹配,且 PCR 扩展值符合预期。
  • 测试异常处理路径:模拟解释器崩溃(如空指针解引用),验证是否触发 EFI 的看门狗定时器并安全回滚至默认引导项。

结论

Promethee 项目揭示了脚本语言深入系统底层的可行性,但其工程化落地依赖于对 ABI 细节的精确控制与纵深防御的安全架构。在资源受限且缺乏操作系统抽象的环境中,任何抽象泄漏都可能演变为系统级漏洞。未来的工作应聚焦于形式化验证 FFI 绑定的正确性,以及探索基于 WebAssembly 的替代方案,后者在字节码验证与线性内存隔离方面提供了更原生的安全保证。

资料来源

  • Promethee 项目仓库(Codeberg.org/smnx/promethee)
  • UEFI 规范 2.10,章节 2.3.4(x86_64 平台调用约定)
  • Hacker News 讨论:UEFI Bindings for JavaScript(item?id=46945348)
查看归档