AArch64 Linux 中的指针认证

作者:Mark Rutland <mark.rutland@arm.com>

日期:2017-07-19

本文档简要描述了 AArch64 Linux 中指针认证功能的提供。

架构概述

ARMv8.3 指针认证扩展添加了可用于缓解某些类攻击的原语,在这些攻击中,攻击者可以破坏某些内存的内容(例如,堆栈)。

该扩展使用指针认证代码 (PAC) 来确定指针是否已被意外修改。 PAC 从指针、另一个值(例如堆栈指针)以及保存在系统寄存器中的密钥派生。

该扩展添加了将有效 PAC 插入指针以及验证/从指针中删除 PAC 的指令。 PAC 占据指针的若干高位,具体取决于配置的虚拟地址大小以及是否使用指针标记。

这些指令的一个子集已从 HINT 编码空间分配。 在没有扩展的情况下(或禁用时),这些指令的行为类似于 NOP。 使用这些指令的应用程序和库可以正确运行,而与扩展的存在无关。

该扩展提供了五个独立的密钥来生成 PAC - 两个用于指令地址 (APIAKey, APIBKey),两个用于数据地址 (APDAKey, APDBKey),一个用于通用身份验证 (APGAKey)。

基本支持

选择 CONFIG_ARM64_PTR_AUTH 并且存在相关的 HW 支持时,内核将在 exec*() 时为每个进程分配随机密钥值。 这些密钥由进程中的所有线程共享,并在 fork() 中保留。

地址认证功能的存在通过 HWCAP_PACA 声明,通用认证功能通过 HWCAP_PACG 声明。

PAC 在指针中占据的位数是 55 减去内核配置的虚拟地址大小。 例如,如果虚拟地址大小为 48,则 PAC 的宽度为 7 位。

选择 ARM64_PTR_AUTH_KERNEL 时,内核将使用 HINT 空间指针认证指令编译,以保护函数返回。 使用此选项构建的内核可在具有或不具有指针认证支持的硬件上运行。

除了 exec() 之外,还可以使用 PR_PAC_RESET_KEYS prctl 将密钥重新初始化为随机值。 PR_PAC_APIAKEY、PR_PAC_APIBKEY、PR_PAC_APDAKEY、PR_PAC_APDBKEY 和 PR_PAC_APGAKEY 的位掩码指定要重新初始化哪些密钥; 指定 0 表示“所有密钥”。

调试

选择 CONFIG_ARM64_PTR_AUTH 并且存在对地址认证的 HW 支持时,内核将在 NT_ARM_PAC_MASK regset(结构 user_pac_mask)中公开 TTBR0 PAC 位的的位置,用户空间可以通过 PTRACE_GETREGSET 获取该位置。

仅当设置了 HWCAP_PACA 时,才公开 regset。 为数据指针和指令指针公开单独的掩码,因为两组 PAC 位可能有所不同。 请注意,这些掩码适用于 TTBR0 地址,并且不适用于 TTBR1 地址(例如,内核指针)。

此外,如果还设置了 CONFIG_CHECKPOINT_RESTORE,内核将公开 NT_ARM_PACA_KEYS 和 NT_ARM_PACG_KEYS regset(结构 user_pac_address_keys 和结构 user_pac_generic_keys)。 这些可用于获取和设置线程的密钥。

虚拟化

当通过传递标志 KVM_ARM_VCPU_PTRAUTH_[ADDRESS/GENERIC] 并在初始化每个虚拟 CPU 时请求启用这两个单独的 CPU 功能时,将在 KVM 客户机中启用指针认证。 当前的 KVM 客户机实现通过同时启用这两个功能来工作,因此在启用指针认证之前会检查这两个用户空间标志。 如果将来添加对独立启用这两个功能的支持,则单独的用户空间标志将允许不进行用户空间 ABI 更改。

由于 Arm 体系结构指定指针认证功能与 VHE 功能一起实现,因此 KVM arm64 ptrauth 代码依赖于 VHE 模式的存在。

此外,如果未设置这些 vcpu 功能标志,则 KVM 将从 KVM_GET/SET_REG_* ioctl 中过滤掉指针认证系统密钥寄存器,并从 cpufeature ID 寄存器中屏蔽这些功能。 任何使用指针认证指令的尝试都将导致将 UNDEFINED 异常注入到客户机中。

启用和禁用密钥

prctl PR_PAC_SET_ENABLED_KEYS 允许用户程序控制在特定任务中启用哪些 PAC 密钥。 它接受两个参数,第一个参数是 PR_PAC_APIAKEY、PR_PAC_APIBKEY、PR_PAC_APDAKEY 和 PR_PAC_APDBKEY 的位掩码,指定此 prctl 应影响哪些密钥,第二个参数是相同位的位掩码,指定是否应启用或禁用密钥。 例如

prctl(PR_PAC_SET_ENABLED_KEYS,
      PR_PAC_APIAKEY | PR_PAC_APIBKEY | PR_PAC_APDAKEY | PR_PAC_APDBKEY,
      PR_PAC_APIBKEY, 0, 0);

禁用除 IB 密钥之外的所有密钥。

这有用的主要原因是启用使用 PAC 指令签名和验证函数指针以及函数外部暴露的其他指针的用户空间 ABI,同时仍然允许符合 ABI 的二进制文件与不签名或验证指针的旧二进制文件互操作。

其思路是,动态加载器或早期启动代码会在确定进程可能加载旧二进制文件之后,但在执行任何 PAC 指令之前,很早地发出此 prctl。

为了与之前的内核版本兼容,进程在启动时启用了 IA、IB、DA 和 DB,并在 exec() 时重置为此状态。 通过 fork() 和 clone() 创建的进程从调用进程继承密钥启用状态。

建议避免禁用 IA 密钥,因为禁用此密钥比禁用任何其他密钥具有更高的性能开销。