看完这篇文章,可以知道AARCH64平台修复meltdown漏洞的KPTI补丁的基本原理。此文很难很分裂,需要具备大量背景知识,慎重阅读。
看不懂也没有关系,记住最后三张页表的结论即可。
一个patch是 "arm64: Kconfig: Add CONFIG_UNMAP_KERNEL_AT_EL0",此patch前后2次修正,地址如下:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=084eb77cd3a81134d02500977dc0ecc9277dc97d
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0617052ddde355ee663b2f048e67dd381e5ebd6a
它其实是,使能了一个从EL1的kernel返回EL0的时候,把kernel unmap掉的功能,这样让user不可见内核:
+config UNMAP_KERNEL_AT_EL0
+bool "Unmap kernel when running in userspace (aka \"KAISER\")"
+default y
+help
+ Some attacks against KASLR make use of the timing difference between
+ a permission fault which could arise from a page table entry that is
+ present in the TLB, and a translation fault which always requires a
+ page table walk. This option defends against these attacks by unmapping
+ the kernel whilst running in userspace, therefore forcing translation
+ faults for all of kernel space.
+
+ If unsure, say Y.
+
第二个patch是"arm64: mm: Map entry trampoline into trampoline and kernel page tables",地址:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=51a0048beb449682d632d0af52a515adb9f9882e
此处建立了一个只包含EL0陷入EL1和退出的必要的代码的页被映射的页表(trampoline page table),
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+static int __init map_entry_trampoline(void)
+{
+extern char __entry_tramp_text_start[];
+
+pgprot_t prot = rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
+phys_addr_t pa_start = __pa_symbol(__entry_tramp_text_start);
+
+/* The trampoline is always mapped and can therefore be global */
+pgprot_val(prot) &= ~PTE_NG;
+
+/* Map only the text into the trampoline page table */
+memset(tramp_pg_dir, 0, PGD_SIZE);
+__create_pgd_mapping(tramp_pg_dir, pa_start, TRAMP_VALIAS, PAGE_SIZE,
+ prot, pgd_pgtable_alloc, 0);
+
+/* ...as well as the kernel page table */
+__set_fixmap(FIX_ENTRY_TRAMP_TEXT, pa_start, prot);
+return 0;
+}
+core_initcall(map_entry_trampoline);
+#endif
第三个patch是“arm64: entry: Add exception trampoline page for exceptions from EL0”,https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c7b9adaf85f818d747eeff5145eb4095ccd587fb
这个patch里面提供了内核完整页表与trampoline页表切换的接口:
+.macro tramp_map_kernel, tmp
+mrs\tmp, ttbr1_el1
+sub\tmp, \tmp, #(SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)
+bic\tmp, \tmp, #USER_ASID_FLAG
+msrttbr1_el1, \tmp
+.endm
+
+.macro tramp_unmap_kernel, tmp
+mrs\tmp, ttbr1_el1
+add\tmp, \tmp, #(SWAPPER_DIR_SIZE + RESERVED_TTBR0_SIZE)
+orr\tmp, \tmp, #USER_ASID_FLAG
+msrttbr1_el1, \tmp
tramp_map_kernel可以让EL0陷入EL1的时候,载入FULL的kernel page table,而 tramp_unmap_kernel可以帮助kernel从EL1退回到EL0的时候,TTBR1里面切换到一个只包含最基本EL0到EL1入口和出口代码的向量表等信息映射的页表(trampoline page table),这样kernel不至于暴露给user。
由此,我们发现AARCH64对meltdown的修复看起来比X86还要复杂,X86好歹只有2个页表,见维基百科:https://en.wikipedia.org/wiki/Kernel_page-table_isolation :
但是ARM64 Linux的需要3个页表:
kernel完整的页表 TTBR1
kernel的只包含trampoline的trampoline页表(这部分完全是为了让EL0陷入和退出EL1用的,并且帮助完成trampoline页表和kernel完整页表的切换)
user的页表 TTBR0