Pointer Authentication
简介
Pointer Authentication是ARM v8.3特性,Qualcomm和Apple的芯片均使能了该功能。Apple在iOS12中引入该功能,并通过PAC实现了用户态以及内核态的CFI,DFI。
PAC利用内存虚拟地址的高位存储内存地址的MAC值来计算和验证指针的完整性,从而保证控制流和数据流的完整性。
如Linux内核文档Documentation/arm64/pointer-authentication.rst中描述,PAC特性使用从54位开始到VA_SIZE位的空闲位存储PAC值。一般虚拟地址有效位为48位,那么用于PAC的就是7位,如果VA_SIZE是52位,那么PAC就只有4位。
与PAC类似的MTE特性,如Documentation/arm64/pointer-authentication.rst所述,则使用虚拟地址的59-56位。
工作原理
PAC的工作原理如下图所示:
假设指针值为ptr,且虚拟地址有效长度为48位,则PAC = ptr[54..48] = QARMA(ptr[47..0], context, key)
。其中QARMA为ARM为PAC设计的MAC算法,算法输入为:
- 内存地址值
- context,或者modifier值。不同的汇编指令对应不同的context值。
- 密钥
PAC相关指令
ARM通过一条指令来计算PAC值,即PAC*
。同样使用一条指令来验证指针的PAC值,即AUT*
指令。 - PACIA Xd, Xn|SP:
- address:Xd
- context:Xn或SP值
- PACIZA Xd
- address:Xd
- context:0
- PACIA1716:
- address:x17
- context:x16
- PACIASP:
- address:x30,即LR寄存器
- context:SP
以上指令都是使用IA密钥。AUT相关指令与PAC指令类似。
密钥管理
PAC共使用5把密钥,每把密钥128位,分别为:
- IA,IB
- DA,DB
- GA
Apple使用了全部5把密钥,应用于不同的场景和数据类型。Linux仅使用了IAkey和PACIASP指令保证函数返回地址的完整性(后向CFI)。
Linux用户态可以使用全部5把密钥1
2
3
4
5
6
7
8
9
10
11/*
* We give each process its own keys, which are shared by all threads. The keys
* are inherited upon fork(), and reinitialised upon exec*().
*/
struct ptrauth_keys_user {
struct ptrauth_key apia;
struct ptrauth_key apib;
struct ptrauth_key apda;
struct ptrauth_key apdb;
struct ptrauth_key apga;
};
Linux内核态尽使用密钥IA1
2
3struct ptrauth_keys_kernel {
struct ptrauth_key apia;
};
Linux内核在进程调用exec系统调用时,为其初始化了所有5把密钥。同时,Linux提供了一个PRCTL,由用户态程序发起对全部5把密钥全部初始化。1
2
3
4
5case PR_PAC_RESET_KEYS:
if (arg3 || arg4 || arg5)
return -EINVAL;
error = PAC_RESET_KEYS(me, arg2);
break;
PAC密钥的使用和设置,都是由内核触发,软件无法获取密钥明文。1
2
3
4
5
6
其中k为APIA,APIB等代表密钥类型的字串。所以最终代表密钥的寄存器形如SYS_APIA_KEYLO_EL1,SYS_APIA_KEYHI_EL1。
Apple PAC
本文对Apple PAC的学习,主要来自于《Demystifying Pointer Authentication on Apple M1 - USENIX23》和两篇BlackHat的演讲:
- iOS Kernel PAC, One Year Later - YouTube
- Behind the scenes of iOS and Mac Security - YouTube
Apple对PAC的应用
可见Apple将PAC广发应用于用户态和内核态的CFI和DFI中。
除了标准的ARM PAC之外,Apple结合其Apple silicon的设计,对PAC进行了增强。
增加寄存器
Apple silicon与PAC有关的寄存器主要有:EXTRAKEY_EL1、VMDIV_EL2和AP_CTL。
- EXTRAKEY_EL1:用于修改实际使用的PAC密钥,以区分用户态密钥和内核态密钥,减少cross EL的攻击
- VMDIV_EL2: 用于在key transformation process时为不同的VM和host派生不同的密钥,降低cross VM的攻击
- AP_CTL:作为ARM SCTLR中PAC开关的补充
- bit 0: Apple PAC总开关
- bit 1,bit 4:分别在用户态和内核态控制EXTRAKEY_EL1
- bit 2,bit3: 分别在用户态和内核态控制Apple PAC开关
改进算法
Apple PAC使用密钥的低64位先与context(modifier)作异或,再输入与密钥的高64位作PAC运算。PAC = QARMA(ARMKey_HI, XOR(ARMKey_LO, context))
增加随机源
Apple PAC与ARM PAC不同的是,Apple PAC不再直接使用硬件寄存器中的密钥,转而使用key transformation process来派生密钥并使用。除了使用密钥寄存器中存储的密钥作为密钥材料之外,还引入了
- VMDIV_EL2:作为cross VM的diversifier。Apple为每个VM以及host使用不同VMDIV_EL2值,确保不同的VM,以及VM与host之间有密钥隔离
- EXTRAKEY_EL1:作为cross EL的diversifier。Apple XNU kernel在用户态使用EXTRAKEY_EL1与密钥进行异或后,作为密钥输入计算PAC值
另外,Apple PAC还引入了per-boot的diversifier,以及per-key的diversifier,即每次重启,Apple PAC均会生成per-boot的随机值,作为key transformation process的随机源。同时,Apple PAC为每把密钥也生成了相应的随机盐值,确保即便被设置成同一个值,实际使用的密钥仍然是不同的。
Linux PAC
gcc支持编译程序时使能PAC。gcc相关参数可参考Using the GNU Compiler Collection (GCC): AArch64 Options
针对C语言代码:1
2
3
4int main()
{
return 0;
}
使用命令gcc -mbranch-protection=pac-ret+leaf main.c -o test
编译可得到下面的汇编代码。其中使用IA key,对函数的返回地址进行校验,从而实现了后向CFI,可以消减ROP攻击风险。1
2
3
4
50000000000000714 <main>:
714: d503233f paciasp
718: 52800000 mov w0, #0x0 // #0
71c: d50323bf autiasp
720: d65f03c0 ret
当前gcc的功能只有这些,远不如Apple的功能丰富。
参考链接
- Demystifying Pointer Authentication on Apple M1 - USENIX23
- iOS Kernel PAC, One Year Later - YouTube
- Behind the scenes of iOS and Mac Security - YouTube
- Pointer Authentication on ARMv8.3 - Qualcomm
- ARM Compiler armasm User Guide Version 6.6