指针认证

简介

指针认证是一种机制,通过这种机制,某些指针会被签名。当一个指针被签名后,它的值以及其他值(pepper 和 salt)的密码学哈希值会被存储在该指针的未使用位中。

在使用指针之前,需要对其进行认证,即检查其签名。这可以防止来源不明的指针值被用来替换已签名的指针值。

有关更多详细信息,请参阅 clang 文档页面 指针认证

在 IR 级别,它通过以下方式表示:

当前的实现利用了 Armv8.3-A PAuth/指针认证代码 指令,这些指令位于 AArch64 后端 中。此支持用于实现 Darwin arm64e ABI,以及 PAuth ABI Extension to ELF

LLVM IR 表示

内建函数

这些内建函数由 LLVM 提供,用于公开指针认证操作。

llvm.ptrauth.sign

语法:
declare i64 @llvm.ptrauth.sign(i64 <value>, i32 <key>, i64 <discriminator>)
概述:

llvm.ptrauth.sign’ 内建函数对原始指针进行签名。

参数:

value 参数是要签名的原始指针值。key 参数是用于生成签名值的密钥的标识符。discriminator 参数是要用作鉴别符的附加多样性数据(整数、地址或两者的混合)。

语义:

llvm.ptrauth.sign’ 内建函数实现了 sign_ 操作。它返回一个已签名值。

如果 value 已经是已签名值,则行为未定义。

如果 value 不是 key 适用的指针值,则行为未定义。

llvm.ptrauth.auth

语法:
declare i64 @llvm.ptrauth.auth(i64 <value>, i32 <key>, i64 <discriminator>)
概述:

llvm.ptrauth.auth’ 内建函数认证已签名的指针。

参数:

value 参数是要认证的已签名指针值。key 参数是用于生成签名值的密钥的标识符。discriminator 参数是要用作鉴别符的附加多样性数据。

语义:

llvm.ptrauth.auth’ 内建函数实现了 auth_ 操作。它返回一个原始指针值。如果 value 没有针对 keydiscriminator 的正确签名,则内建函数会以目标特定的方式陷入陷阱。

llvm.ptrauth.strip

语法:
declare i64 @llvm.ptrauth.strip(i64 <value>, i32 <key>)
概述:

llvm.ptrauth.strip’ 内建函数从可能已签名的指针中剥离嵌入的签名。

参数:

value 参数是要剥离的已签名指针值。key 参数是用于生成签名值的密钥的标识符。

语义:

llvm.ptrauth.strip’ 内建函数实现了 strip_ 操作。它返回一个原始指针值。它检查签名是否有效。

key 应该标识一个适用于 value 的密钥,如目标特定的 密钥 中所定义的那样。

如果 value 是原始指针值,则按原样返回(前提是 key 适用于该指针)。

如果 value 不是 key 适用的指针值,则行为是目标特定的。

如果 value 是已签名指针值,但 key 未标识用于生成 value 的同一密钥,则行为是目标特定的。

llvm.ptrauth.resign

语法:
declare i64 @llvm.ptrauth.resign(i64 <value>,
                                 i32 <old key>, i64 <old discriminator>,
                                 i32 <new key>, i64 <new discriminator>)
概述:

llvm.ptrauth.resign’ 内建函数使用不同的密钥和多样性数据重新签名已签名的指针。

参数:

value 参数是要认证的已签名指针值。old key 参数是用于生成签名值的密钥的标识符。old discriminator 参数是在认证操作中用作鉴别符的附加多样性数据。new key 参数是用于生成重新签名值的密钥的标识符。new discriminator 参数是在签名操作中用作鉴别符的附加多样性数据。

语义:

llvm.ptrauth.resign’ 内建函数执行组合的 auth_ 和 sign_ 操作,而不会暴露中间的原始指针。它返回一个已签名指针值。如果 value 没有针对 old keyold discriminator 的正确签名,则内建函数会以目标特定的方式陷入陷阱。

llvm.ptrauth.sign_generic

语法:
declare i64 @llvm.ptrauth.sign_generic(i64 <value>, i64 <discriminator>)
概述:

llvm.ptrauth.sign_generic’ 内建函数计算任意数据的通用签名。

参数:

value 参数是要签名的任意数据值。discriminator 参数是要用作鉴别符的附加多样性数据。

语义:

llvm.ptrauth.sign_generic’ 内建函数计算给定值和附加多样性数据的组合的签名。

它返回完整的签名值(而不是带有嵌入式部分签名的已签名指针值)。

llvm.ptrauth.sign 不同,它不将 value 解释为指针值。相反,它是一个任意数据值。

llvm.ptrauth.blend

语法:
declare i64 @llvm.ptrauth.blend(i64 <address discriminator>, i64 <integer discriminator>)
概述:

llvm.ptrauth.blend’ 内建函数将指针地址鉴别符与小整数鉴别符混合,以生成新的“混合”鉴别符。

参数:

address discriminator 参数是指针值。integer discriminator 参数是一个小整数,由目标指定。

语义:

llvm.ptrauth.blend’ 内建函数将小整数鉴别符与指针地址鉴别符组合,组合方式由目标实现指定。

常量

内建函数 可以用于在代码中动态生成已签名指针,但不能用于常量引用的已签名指针,例如,全局初始化器中的指针。

后者使用 ptrauth 常量 表示,该常量描述生成已签名指针的已认证重定位。

ptrauth (ptr CST, i32 KEY, i64 DISC, ptr ADDRDISC)

等效于

  %disc = call i64 @llvm.ptrauth.blend(i64 ptrtoint(ptr ADDRDISC to i64), i64 DISC)
  %signedval = call i64 @llvm.ptrauth.sign(ptr CST, i32 KEY, i64 %disc)

操作数束

用作间接调用目标的函数指针可以在物化时签名,并在调用前进行认证。这可以使用 llvm.ptrauth.auth 内建函数完成,将其结果馈送到间接调用中。

但是,这会暴露中间的、未经认证的指针,例如,如果它被溢出到堆栈中。攻击者随后可以覆盖内存中的指针,从而抵消指针认证提供的安全优势。为了防止这种情况,可以使用 ptrauth 操作数束:它保证中间调用目标保留在寄存器中,并且永远不会存储到内存中。这种强化优势类似于 llvm.ptrauth.resign 提供的优势。

具体来说

define void @f(void ()* %fp) {
  call void %fp() [ "ptrauth"(i32 <key>, i64 <data>) ]
  ret void
}

在功能上等效于

define void @f(void ()* %fp) {
  %fp_i = ptrtoint void ()* %fp to i64
  %fp_auth = call i64 @llvm.ptrauth.auth(i64 %fp_i, i32 <key>, i64 <data>)
  %fp_auth_p = inttoptr i64 %fp_auth to void ()*
  call void %fp_auth_p()
  ret void
}

但增加了保证 %fp_i%fp_auth%fp_auth_p 不会存储到(以及从)内存。

函数属性

某些函数属性用于描述其他指针认证操作,这些操作在 IR 中没有以其他方式显式表达。

ptrauth-indirect-gotos

ptrauth-indirect-gotos 指定此函数中的间接 goto 语句应认证其目标。在 IR 级别,不需要其他更改。当降低 blockaddress 常量indirectbr 指令 时,这告诉后端分别签名和认证指针。

具体方案在 ABI 中不可见。目前,AArch64 后端使用 ASIA 密钥对 blockaddress 进行签名,并使用从父函数的名称派生的整数鉴别符,使用 SipHash 稳定鉴别符

  ptrauth_string_discriminator("<function_name> blockaddress")

AArch64 支持

AArch64 是目前唯一完全支持基于 Armv8.3-A 指令的指针认证原语的架构。

Armv8.3-A PAuth 指针认证代码

Armv8.3-A 架构扩展定义了 PAuth 功能,该功能为操作指针认证代码 (PAC) 的指令提供支持。

密钥

PAuth 功能支持 5 个密钥。

其中,4 个密钥可互换使用,以指定 IR 构造中使用的密钥

  • ASIA/ASIB 是指令密钥(分别编码为 0 和 1)。

  • ASDA/ASDB 是数据密钥(分别编码为 2 和 3)。

ASGA 是一个特殊的密钥,不能显式指定,并且仅隐式使用,以实现 llvm.ptrauth.sign_generic 内建函数。

指令

上面描述的 IR 内建函数 映射到这些指令,如下所示

汇编表示

在汇编级别,经过身份验证的重定位使用 @AUTH 修饰符表示

    .quad _target@AUTH(<key>,<discriminator>[,addr])

其中

  • key 是 Armv8.3-A 密钥标识符(iaibdadb

  • discriminator 是 16 位无符号鉴别符值

  • addr 表示经过身份验证的指针是地址鉴别的(也就是说,重定位的目标地址将被混合到 discriminator 中,然后再用于签名操作中。)

例如

  _authenticated_reference_to_sym:
    .quad _sym@AUTH(db,0)
  _authenticated_reference_to_sym_addr_disc:
    .quad _sym@AUTH(ia,12,addr)

MachO 目标文件表示

在目标文件级别,经过身份验证的重定位使用 ARM64_RELOC_AUTHENTICATED_POINTER 重定位类型(值为 11)表示。

指针身份验证信息编码到附加值中,如下所示

| 63 | 62 | 61-51 | 50-49 |   48   | 47     -     32 | 31  -  0 |
| -- | -- | ----- | ----- | ------ | --------------- | -------- |
|  1 |  0 |   0   |  key  |  addr  |  discriminator  |  addend  |

ELF 目标文件表示

在目标文件级别,经过身份验证的重定位使用 R_AARCH64_AUTH_ABS64 重定位类型(值为 0xE100)表示。

签名方案编码在要应用的重定位位置,如下所示

| 63                | 62       | 61:60    | 59:48    |  47:32        | 31:0                |
| ----------------- | -------- | -------- | -------- | ------------- | ------------------- |
| address diversity | reserved | key      | reserved | discriminator | reserved for addend |