ARM64 PAC 相关场景与代码流程

代码所在地

功能代码

  • arch/arm64/kernel/pointer_auth.c
  • arch/arm64/include/asm/pointer_auth.h
  • arch/arm64/include/asm/asm_pointer_auth.h(这个容易漏掉!)

测试代码

  • tools/testing/selftests/arm64/pauth/*,用户态测试
    • TEST(corrupt_pac):测试篡改带 PAC 的 LR 是否会导致验签失败从而令用户态进程收到信号
    • TEST(pac_instructions_not_nop{,_generic}):测试 pac* 指令是否生成了签名
    • TEST(single_thread_different_keys):测试各秘钥的值是否不同
    • TEST(exec_changed_keys):测试 exec 后秘钥是否改变
    • TEST(context_switch_keep_keys{,_generic}):测试进程上下文切换前后秘钥是否保持一致
  • LKDTM:drivers/misc/lkdtm/bugs.c补丁链接 ),内核态测试
    • 在一个函数中修改内核态秘钥,看函数返回时会不会验签失败

相关场景

(新)进程执行 exec

更新该进程所有的用户态秘钥,并切换除 IA 以外的秘钥。至于 IA,要等到从内核态退出至用户态时切换,该场景后面有描述。

 1load_elf_binary(...)
 2  setup_new_exec(...)
 3    arch_setup_new_exec(...)
 4      ptrauth_thread_init_user(...)
 5        ptrauth_keys_init_user(keys:&current->thread.keys_user)
 6          get_random_bytes(&keys->ap{i,d}{a,b}, ...);
 7          ptrauth_keys_install_user(keys)
 8            __ptrauth_key_install_nosync(k:AP{IB,DA,DB}, v:keys->ap{ib,da,db})
 9              write_sysreg_s(v.{lo,hi}, SYS_ ## k ## KEY{LO,HI}_EL1)
10                asm volatile(__msr_s ...)
11        ptrauth_set_enabled_keys(current, ...)

各进程保存一份自己的秘钥在 current->thread.keys_{user,kernel}。进程在用户态可用 5 个秘钥:{I,D}{A,B},GA,而在内核态只用 1 个秘钥:IA。内核态 IA(kernel IA)和用户态 IA(user IA)不是一回事,在内核态↔用户态时要实施切换。

进程切换

切换用户态非 IA 和内核态 IA,将新进程的这些秘钥载入 CPU 的系统寄存器中。先切用户态秘钥(在内核态切用户态秘钥无所谓),在进程切换的最后阶段才切内核态 IA。

 1switch_to(prev, next, last)
 2  (last) = __switch_to((prev), (next)) {
 3    ptrauth_thread_switch_user(tsk:next)
 4      ptrauth_keys_install_user(&(tsk)->thread.keys_user)
 5        ... // same as above
 6    last = cpu_switch_to(prev, next)
 7      ... // main context switch
 8      ptrauth_keys_install_kernel x1, x8, x9, x10
 9        __ptrauth_keys_install_kernel_nosync \tsk, \tmp1, \tmp2, \tmp3
10          mov     \tmp1, #THREAD_KEYS_KERNEL
11          add     \tmp1, \tsk, \tmp1
12          ldp     \tmp2, \tmp3, [\tmp1, #PTRAUTH_KERNEL_KEY_APIA]
13          msr_s   SYS_APIAKEYLO_EL1, \tmp2
14          msr_s	  SYS_APIAKEYHI_EL1, \tmp3
15  }

进程创建

为新进程创建内核态秘钥,只创建不切换。秘钥切换要待到新进程被调度时才实施,如上一个场景所描述的那样。

1copy_process()
2  struct task_struct *p = dup_task_struct(current, ...);
3  copy_thread(p, args)
4    ptrauth_thread_init_kernel(tsk:p)
5      ptrauth_keys_init_kernel(&(tsk)->thread.keys_kernel)
6        get_random_bytes(&keys->apia, sizeof(keys->apia))

内核启动

为初始进程创建内核态秘钥并实施加载。

1start_kernel(void)
2  boot_init_stack_canary(void)
3    ptrauth_thread_init_kernel(current)
4      ... // same as above
5    ptrauth_thread_switch_kernel(current)
6      __ptrauth_key_install_nosync(APIA, keys->apia)
7    ptrauth_enable()

内核态进入/退出(Kernel Entry/Exit)

如上所述,用户态 IA 和内核态 IA 是两个不同的秘钥。在进程从用户态陷入内核态时,需要将用户态 IA 切换成内核态 IA,即将后者载入 CPU 系统寄存器;在进程退出内核态时则反之。

 1/* arch/arm64/kernel/entry.S */
 2	.macro  kernel_ventry, el:req, ht:req, regsize:req, label:req
 3	...
 4	b       el\el\ht\()_\regsize\()_\label
 5	...
 6	.endm
 7
 8	.macro  entry_handler el:req, ht:req, regsize:req, label:req
 9SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
10	kernel_entry \el, \regsize
11	...
12	.if \el == 0
13	b       ret_to_user
14	.else
15	b       ret_to_kernel
16SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
17	.endm
18
19SYM_CODE_START_LOCAL(ret_to_kernel)
20	kernel_exit 1
21SYM_CODE_END(ret_to_kernel)
22
23SYM_CODE_START_LOCAL(ret_to_user)
24	...
25	kernel_exit 0
26SYM_CODE_END(ret_to_user)
27
28	.macro  kernel_entry, el, regsize = 64
29	...
30	ldr     x0, [tsk, THREAD_SCTLR_USER]
31	...
32	tbz     x0, SCTLR_ELx_ENIA_SHIFT, 1f
33	...
34	__ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
35	b       2f
361:
37	mrs     x0, sctlr_el1
38	orr     x0, x0, SCTLR_ELx_ENIA
39	msr     sctlr_el1, x0
402:
41	...
42	.endm
43
44	.macro  kernel_exit, el
45	...
46	ldr     x0, [tsk, THREAD_SCTLR_USER]
47	...
48	tbz     x0, SCTLR_ELx_ENIA_SHIFT, 1f
49	__ptrauth_keys_install_user tsk, x0, x1, x2
50	b       2f
511:
52	mrs     x0, sctlr_el1
53	bic     x0, x0, SCTLR_ELx_ENIA
54	msr     sctlr_el1, x0
552:
56	...
57	.endm

注意:这里的 kernel_entry 是指进入内核态的操作(“entering kernel”),对应 kernel_exit;而 kernel_ventry(多个 v)是指异常向量表的条目(“(exception) vector entry”)。

CPU Resume/Suspend

这个场景不是很懂,大概是关于 CPU 进入/退出低功耗 idle 状态。

 1cpu_resume
 2  ldr     x8, =_cpu_resume
 3  br      x8
 4    bl cpu_do_resume
 5      ptrauth_keys_install_kernel_nosync ...
 6
 7cpu_suspend(arg, fn)
 8  ret = fn(arg)
 9  /* Successful cpu_suspend() should return from cpu_resume() */
10  __cpu_suspend_exit(void)
11    ptrauth_suspend_exit()
12      ptrauth_keys_install_user(&current->thread.keys_user)

用户态进程:如何创建,如何销毁
SELinux:给文件单独打标签