指针认证¶
简介¶
指针认证是一种机制,通过这种机制,某些指针会被签名。当一个指针被签名后,它的值以及其他值(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
没有针对 key
和 discriminator
的正确签名,则内建函数会以目标特定的方式陷入陷阱。
‘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 key
和 old 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 内建函数 映射到这些指令,如下所示
llvm.ptrauth.sign
:PAC{I,D}{A,B}{Z,SP,}
llvm.ptrauth.auth
:AUT{I,D}{A,B}{Z,SP,}
llvm.ptrauth.strip
:XPAC{I,D}
llvm.ptrauth.blend
: blend 操作的语义由 ABI 指定。在 ELF PAuth ABI 扩展和 arm64e 中,它是一个进入高 16 位的MOVK
。因此,这限制了 blend 中使用的整数鉴别符的宽度为 16 位。llvm.ptrauth.sign_generic
:PACGA
llvm.ptrauth.resign
:AUT*+PAC*
。这些在后端表示为单个伪指令,以保证中间原始指针值不会被溢出和攻击。
汇编表示¶
在汇编级别,经过身份验证的重定位使用 @AUTH
修饰符表示
.quad _target@AUTH(<key>,<discriminator>[,addr])
其中
key
是 Armv8.3-A 密钥标识符(ia
、ib
、da
、db
)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 |