KPTI 简述
KPTI (Kernel Page Table Isolation) 机制最初的主要目的是为了缓解 KASLR 的绕过以及 CPU 侧信道攻击。
在 KPTI 机制中,内核态空间的内存和用户态空间的内存的隔离进一步得到了增强。
- 内核态中的页表包括用户空间内存的页表和内核空间内存的页表。
- 用户态的页表只包括用户空间内存的页表以及必要的内核空间内存的页表,如用于处理系统调用、中断等信息的内存。
简而言之,KPTI 机制通过在系统调用等由用户态陷入内核态的过程中切换页表,使得内核态不能直接访问用户态的虚拟内存和执行用户态的代码段来避免熔断安全漏洞。
xv6-riscv 中的 KPTI
上下文切换
关于用户态陷入内核态的上下文切换,定义在 kernel/trampoline.S
中的 uservec
中:
1 | uservec: |
可以看到 csrw satp, t1
这条语句将用户态的页表切换成了内核态的页表,也就是说,在内核态中尽管可以访问用户态页表,但无法直接通过虚拟内存访问用户态代码段,因为我们没有在内核页表中为用户代码段直接做映射。
内核访问用户态数据
关于内核如何访问用户态数据定义在 kernel/vm.c
的 copyout
和 copyin
函数中:
1 | // Copy from kernel to user. |
可以看到,为了访问用户态数据,我们需要首先将用户态的虚拟地址经过用户态页表进行地址翻译转成物理地址,之后再通过物理地址直接访问用户态数据即可,那么由于在内核中的数据段是进行 1 : 1 映射的,因此内存访问在经过 MMU 地址翻译的时候不会出错,关于内核地址映射定义在 kernel/vm.c
的 kvmmake
函数中:
1 | // Make a direct-map page table for the kernel. |