From: Madhavan T. Venkataraman <madvenka@xxxxxxxxxxxxxxxxxxx> When permissions are changed on an existing mapping, update the permissions counters. Cc: Borislav Petkov <bp@xxxxxxxxx> Cc: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx> Cc: H. Peter Anvin <hpa@xxxxxxxxx> Cc: Ingo Molnar <mingo@xxxxxxxxxx> Cc: Kees Cook <keescook@xxxxxxxxxxxx> Cc: Madhavan T. Venkataraman <madvenka@xxxxxxxxxxxxxxxxxxx> Cc: Mickaël Salaün <mic@xxxxxxxxxxx> Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx> Cc: Sean Christopherson <seanjc@xxxxxxxxxx> Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx> Cc: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx> Cc: Wanpeng Li <wanpengli@xxxxxxxxxxx> Signed-off-by: Madhavan T. Venkataraman <madvenka@xxxxxxxxxxxxxxxxxxx> --- Changes since v1: * New patch --- arch/x86/mm/heki.c | 9 +++++++ arch/x86/mm/pat/set_memory.c | 51 ++++++++++++++++++++++++++++++++++++ include/linux/heki.h | 14 ++++++++++ virt/heki/counters.c | 23 ++++++++++++++++ 4 files changed, 97 insertions(+) diff --git a/arch/x86/mm/heki.c b/arch/x86/mm/heki.c index c495df0d8772..c0eace9e343f 100644 --- a/arch/x86/mm/heki.c +++ b/arch/x86/mm/heki.c @@ -54,3 +54,12 @@ unsigned long heki_flags_to_permissions(unsigned long flags) return permissions; } + +void heki_pgprot_to_permissions(pgprot_t prot, unsigned long *set, + unsigned long *clear) +{ + if (pgprot_val(prot) & _PAGE_RW) + *set |= MEM_ATTR_WRITE; + if (pgprot_val(prot) & _PAGE_NX) + *clear |= MEM_ATTR_EXEC; +} diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index bda9f129835e..6aaa1ce5692c 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -22,6 +22,7 @@ #include <linux/cc_platform.h> #include <linux/set_memory.h> #include <linux/memregion.h> +#include <linux/heki.h> #include <asm/e820/api.h> #include <asm/processor.h> @@ -2056,11 +2057,56 @@ int clear_mce_nospec(unsigned long pfn) EXPORT_SYMBOL_GPL(clear_mce_nospec); #endif /* CONFIG_X86_64 */ +#ifdef CONFIG_HEKI + +static void heki_change_page_attr_set(unsigned long va, int numpages, + pgprot_t set) +{ + unsigned long va_end; + unsigned long set_permissions = 0, clear_permissions = 0; + + heki_pgprot_to_permissions(set, &set_permissions, &clear_permissions); + if (!(set_permissions | clear_permissions)) + return; + + va_end = va + (numpages << PAGE_SHIFT); + heki_update(va, va_end, set_permissions, clear_permissions); +} + +static void heki_change_page_attr_clear(unsigned long va, int numpages, + pgprot_t clear) +{ + unsigned long va_end; + unsigned long set_permissions = 0, clear_permissions = 0; + + heki_pgprot_to_permissions(clear, &clear_permissions, &set_permissions); + if (!(set_permissions | clear_permissions)) + return; + + va_end = va + (numpages << PAGE_SHIFT); + heki_update(va, va_end, set_permissions, clear_permissions); +} + +#else /* !CONFIG_HEKI */ + +static void heki_change_page_attr_set(unsigned long va, int numpages, + pgprot_t set) +{ +} + +static void heki_change_page_attr_clear(unsigned long va, int numpages, + pgprot_t clear) +{ +} + +#endif /* CONFIG_HEKI */ + int set_memory_x(unsigned long addr, int numpages) { if (!(__supported_pte_mask & _PAGE_NX)) return 0; + heki_change_page_attr_clear(addr, numpages, __pgprot(_PAGE_NX)); return change_page_attr_clear(&addr, numpages, __pgprot(_PAGE_NX), 0); } @@ -2069,11 +2115,14 @@ int set_memory_nx(unsigned long addr, int numpages) if (!(__supported_pte_mask & _PAGE_NX)) return 0; + heki_change_page_attr_set(addr, numpages, __pgprot(_PAGE_NX)); return change_page_attr_set(&addr, numpages, __pgprot(_PAGE_NX), 0); } int set_memory_ro(unsigned long addr, int numpages) { + // TODO: What about _PAGE_DIRTY? + heki_change_page_attr_clear(addr, numpages, __pgprot(_PAGE_RW)); return change_page_attr_clear(&addr, numpages, __pgprot(_PAGE_RW | _PAGE_DIRTY), 0); } @@ -2084,11 +2133,13 @@ int set_memory_rox(unsigned long addr, int numpages) if (__supported_pte_mask & _PAGE_NX) clr.pgprot |= _PAGE_NX; + heki_change_page_attr_clear(addr, numpages, clr); return change_page_attr_clear(&addr, numpages, clr, 0); } int set_memory_rw(unsigned long addr, int numpages) { + heki_change_page_attr_set(addr, numpages, __pgprot(_PAGE_RW)); return change_page_attr_set(&addr, numpages, __pgprot(_PAGE_RW), 0); } diff --git a/include/linux/heki.h b/include/linux/heki.h index d660994d34d0..079b34af07f0 100644 --- a/include/linux/heki.h +++ b/include/linux/heki.h @@ -73,6 +73,7 @@ struct heki_hypervisor { * * - a page is mapped into the kernel address space * - a page is unmapped from the kernel address space + * - permissions are changed for a mapped page */ struct heki { struct heki_hypervisor *hypervisor; @@ -81,6 +82,7 @@ struct heki { enum heki_cmd { HEKI_MAP, + HEKI_UPDATE, HEKI_UNMAP, }; @@ -98,6 +100,10 @@ struct heki_args { /* Command passed by caller. */ enum heki_cmd cmd; + + /* Permissions passed by heki_update(). */ + unsigned long set; + unsigned long clear; }; /* Callback function called by the table walker. */ @@ -114,11 +120,15 @@ void heki_counters_init(void); void heki_walk(unsigned long va, unsigned long va_end, heki_func_t func, struct heki_args *args); void heki_map(unsigned long va, unsigned long end); +void heki_update(unsigned long va, unsigned long end, unsigned long set, + unsigned long clear); void heki_unmap(unsigned long va, unsigned long end); /* Arch-specific functions. */ void heki_arch_early_init(void); unsigned long heki_flags_to_permissions(unsigned long flags); +void heki_pgprot_to_permissions(pgprot_t prot, unsigned long *set, + unsigned long *clear); #else /* !CONFIG_HEKI */ @@ -131,6 +141,10 @@ static inline void heki_late_init(void) static inline void heki_map(unsigned long va, unsigned long end) { } +static inline void heki_update(unsigned long va, unsigned long end, + unsigned long set, unsigned long clear) +{ +} static inline void heki_unmap(unsigned long va, unsigned long end) { } diff --git a/virt/heki/counters.c b/virt/heki/counters.c index adc8d566b8a9..d0f830b0775a 100644 --- a/virt/heki/counters.c +++ b/virt/heki/counters.c @@ -88,6 +88,13 @@ void heki_callback(struct heki_args *args) heki_update_counters(counters, 0, permissions, 0); break; + case HEKI_UPDATE: + if (!counters) + continue; + heki_update_counters(counters, permissions, args->set, + args->clear); + break; + case HEKI_UNMAP: if (WARN_ON_ONCE(!counters)) break; @@ -131,6 +138,22 @@ void heki_map(unsigned long va, unsigned long end) heki_func(va, end, &args); } +/* + * Find the mappings in the given range and update permission counters for + * them. Apply permissions in the host page table. + */ +void heki_update(unsigned long va, unsigned long end, unsigned long set, + unsigned long clear) +{ + struct heki_args args = { + .cmd = HEKI_UPDATE, + .set = set, + .clear = clear, + }; + + heki_func(va, end, &args); +} + /* * Find the mappings in the given range and revert the permission counters for * them. -- 2.42.1