代码所在地
功能代码
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:¤t->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(¤t->thread.keys_user)