指针认证¶
简介¶
指针认证是一种对某些指针进行签名的机制。当指针被签名时,其值和其他值(胡椒和盐)的加密哈希将存储在该指针的未使用位中。
在使用指针之前,需要对其进行身份验证,即检查其签名。这可以防止使用未知来源的指针值替换已签名的指针值。
有关更多详细信息,请参阅 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
没有 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
参数是要在 auth 操作中用作鉴别符的其他多样性数据。new key
参数是要用于生成重新签名值的密钥的标识符。new discriminator
参数是要在 sign 操作中用作鉴别符的其他多样性数据。
语义:¶
‘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
:混合操作的语义由 ABI 指定。在 ELF PAuth ABI 扩展和 arm64e 中,它都是对高 16 位的MOVK
。因此,这将混合中使用的整数鉴别器的宽度限制为 16 位。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 |