提交 85900ea5 编写于 作者: A Andy Lutomirski 提交者: Ingo Molnar

x86/pti: Map the vsyscall page if needed

Make VSYSCALLs work fully in PTI mode by mapping them properly to the user
space visible page tables.

[ tglx: Hide unused functions (Patch by Arnd Bergmann) ]
Signed-off-by: NAndy Lutomirski <luto@kernel.org>
Signed-off-by: NThomas Gleixner <tglx@linutronix.de>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: David Laight <David.Laight@aculab.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Juergen Gross <jgross@suse.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: NIngo Molnar <mingo@kernel.org>
上级 f55f0501
...@@ -344,14 +344,14 @@ int in_gate_area_no_mm(unsigned long addr) ...@@ -344,14 +344,14 @@ int in_gate_area_no_mm(unsigned long addr)
* vsyscalls but leave the page not present. If so, we skip calling * vsyscalls but leave the page not present. If so, we skip calling
* this. * this.
*/ */
static void __init set_vsyscall_pgtable_user_bits(void) void __init set_vsyscall_pgtable_user_bits(pgd_t *root)
{ {
pgd_t *pgd; pgd_t *pgd;
p4d_t *p4d; p4d_t *p4d;
pud_t *pud; pud_t *pud;
pmd_t *pmd; pmd_t *pmd;
pgd = pgd_offset_k(VSYSCALL_ADDR); pgd = pgd_offset_pgd(root, VSYSCALL_ADDR);
set_pgd(pgd, __pgd(pgd_val(*pgd) | _PAGE_USER)); set_pgd(pgd, __pgd(pgd_val(*pgd) | _PAGE_USER));
p4d = p4d_offset(pgd, VSYSCALL_ADDR); p4d = p4d_offset(pgd, VSYSCALL_ADDR);
#if CONFIG_PGTABLE_LEVELS >= 5 #if CONFIG_PGTABLE_LEVELS >= 5
...@@ -373,7 +373,7 @@ void __init map_vsyscall(void) ...@@ -373,7 +373,7 @@ void __init map_vsyscall(void)
vsyscall_mode == NATIVE vsyscall_mode == NATIVE
? PAGE_KERNEL_VSYSCALL ? PAGE_KERNEL_VSYSCALL
: PAGE_KERNEL_VVAR); : PAGE_KERNEL_VVAR);
set_vsyscall_pgtable_user_bits(); set_vsyscall_pgtable_user_bits(swapper_pg_dir);
} }
BUILD_BUG_ON((unsigned long)__fix_to_virt(VSYSCALL_PAGE) != BUILD_BUG_ON((unsigned long)__fix_to_virt(VSYSCALL_PAGE) !=
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#ifdef CONFIG_X86_VSYSCALL_EMULATION #ifdef CONFIG_X86_VSYSCALL_EMULATION
extern void map_vsyscall(void); extern void map_vsyscall(void);
extern void set_vsyscall_pgtable_user_bits(pgd_t *root);
/* /*
* Called on instruction fetch fault in vsyscall page. * Called on instruction fetch fault in vsyscall page.
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include <asm/cpufeature.h> #include <asm/cpufeature.h>
#include <asm/hypervisor.h> #include <asm/hypervisor.h>
#include <asm/vsyscall.h>
#include <asm/cmdline.h> #include <asm/cmdline.h>
#include <asm/pti.h> #include <asm/pti.h>
#include <asm/pgtable.h> #include <asm/pgtable.h>
...@@ -223,6 +224,69 @@ static pmd_t *pti_user_pagetable_walk_pmd(unsigned long address) ...@@ -223,6 +224,69 @@ static pmd_t *pti_user_pagetable_walk_pmd(unsigned long address)
return pmd_offset(pud, address); return pmd_offset(pud, address);
} }
#ifdef CONFIG_X86_VSYSCALL_EMULATION
/*
* Walk the shadow copy of the page tables (optionally) trying to allocate
* page table pages on the way down. Does not support large pages.
*
* Note: this is only used when mapping *new* kernel data into the
* user/shadow page tables. It is never used for userspace data.
*
* Returns a pointer to a PTE on success, or NULL on failure.
*/
static __init pte_t *pti_user_pagetable_walk_pte(unsigned long address)
{
gfp_t gfp = (GFP_KERNEL | __GFP_NOTRACK | __GFP_ZERO);
pmd_t *pmd = pti_user_pagetable_walk_pmd(address);
pte_t *pte;
/* We can't do anything sensible if we hit a large mapping. */
if (pmd_large(*pmd)) {
WARN_ON(1);
return NULL;
}
if (pmd_none(*pmd)) {
unsigned long new_pte_page = __get_free_page(gfp);
if (!new_pte_page)
return NULL;
if (pmd_none(*pmd)) {
set_pmd(pmd, __pmd(_KERNPG_TABLE | __pa(new_pte_page)));
new_pte_page = 0;
}
if (new_pte_page)
free_page(new_pte_page);
}
pte = pte_offset_kernel(pmd, address);
if (pte_flags(*pte) & _PAGE_USER) {
WARN_ONCE(1, "attempt to walk to user pte\n");
return NULL;
}
return pte;
}
static void __init pti_setup_vsyscall(void)
{
pte_t *pte, *target_pte;
unsigned int level;
pte = lookup_address(VSYSCALL_ADDR, &level);
if (!pte || WARN_ON(level != PG_LEVEL_4K) || pte_none(*pte))
return;
target_pte = pti_user_pagetable_walk_pte(VSYSCALL_ADDR);
if (WARN_ON(!target_pte))
return;
*target_pte = *pte;
set_vsyscall_pgtable_user_bits(kernel_to_user_pgdp(swapper_pg_dir));
}
#else
static void __init pti_setup_vsyscall(void) { }
#endif
static void __init static void __init
pti_clone_pmds(unsigned long start, unsigned long end, pmdval_t clear) pti_clone_pmds(unsigned long start, unsigned long end, pmdval_t clear)
{ {
...@@ -319,4 +383,5 @@ void __init pti_init(void) ...@@ -319,4 +383,5 @@ void __init pti_init(void)
pti_clone_user_shared(); pti_clone_user_shared();
pti_clone_entry_text(); pti_clone_entry_text();
pti_setup_espfix64(); pti_setup_espfix64();
pti_setup_vsyscall();
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册