指针认证

简介

指针认证是一种对某些指针进行签名的机制。当指针被签名时,其值和其他值(胡椒和盐)的加密哈希将存储在该指针的未使用位中。

在使用指针之前,需要对其进行身份验证,即检查其签名。这可以防止使用未知来源的指针值替换已签名的指针值。

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

在 IR 层面,它使用以下方式表示:

  • 一组内联函数(用于签名/认证指针)

  • 一个已签名指针常量(用于签名全局变量)

  • 一个调用操作数捆绑(用于认证被调用的指针)

  • 一组函数属性(用于描述哪些指针已签名以及如何签名,以控制后端中的隐式代码生成,以及在中级优化器中保留不变式)

当前的实现利用了 Armv8.3-A PAuth/指针认证代码 指令在AArch64 后端中。此支持用于实现 Darwin arm64e ABI,以及PAuth ABI 对 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 参数是要在 auth 操作中用作鉴别符的其他多样性数据。new key 参数是要用于生成重新签名值的密钥的标识符。new discriminator 参数是要在 sign 操作中用作鉴别符的其他多样性数据。

语义:

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 |