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内核态尽使用密钥IA
1
2
3
struct ptrauth_keys_kernel {
struct ptrauth_key apia;
};

Linux内核在进程调用exec系统调用时,为其初始化了所有5把密钥。同时,Linux提供了一个PRCTL,由用户态程序发起对全部5把密钥全部初始化。
1
2
3
4
5
case PR_PAC_RESET_KEYS:
if (arg3 || arg4 || arg5)
return -EINVAL;
error = PAC_RESET_KEYS(me, arg2);
break;

PAC密钥的使用和设置,都是由内核触发,软件无法获取密钥明文。
1
2
3
4
5
6
#define __ptrauth_key_install_nosync(k, v)			\
do { \
struct ptrauth_key __pki_v = (v); \
write_sysreg_s(__pki_v.lo, SYS_ ## k ## KEYLO_EL1); \
write_sysreg_s(__pki_v.hi, SYS_ ## k ## KEYHI_EL1); \
} while (0)

其中k为APIA,APIB等代表密钥类型的字串。所以最终代表密钥的寄存器形如SYS_APIA_KEYLO_EL1,SYS_APIA_KEYHI_EL1。

Apple PAC

本文对Apple PAC的学习,主要来自于《Demystifying Pointer Authentication on Apple M1 - USENIX23》和两篇BlackHat的演讲:

增加寄存器

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
4
int main()
{
return 0;
}

使用命令gcc -mbranch-protection=pac-ret+leaf main.c -o test编译可得到下面的汇编代码。其中使用IA key,对函数的返回地址进行校验,从而实现了后向CFI,可以消减ROP攻击风险。
1
2
3
4
5
0000000000000714 <main>:
714: d503233f paciasp
718: 52800000 mov w0, #0x0 // #0
71c: d50323bf autiasp
720: d65f03c0 ret

当前gcc的功能只有这些,远不如Apple的功能丰富。

参考链接