Message ID | 20240605093006.145492-10-steven.price@arm.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | arm64: Support for running as a guest in Arm CCA | expand |
On Wed, Jun 05, 2024 at 10:30:01AM +0100, Steven Price wrote: > +static int __set_memory_encrypted(unsigned long addr, > + int numpages, > + bool encrypt) > +{ > + unsigned long set_prot = 0, clear_prot = 0; > + phys_addr_t start, end; > + int ret; > + > + if (!is_realm_world()) > + return 0; > + > + if (!__is_lm_address(addr)) > + return -EINVAL; > + > + start = __virt_to_phys(addr); > + end = start + numpages * PAGE_SIZE; > + > + /* > + * Break the mapping before we make any changes to avoid stale TLB > + * entries or Synchronous External Aborts caused by RIPAS_EMPTY > + */ > + ret = __change_memory_common(addr, PAGE_SIZE * numpages, > + __pgprot(0), > + __pgprot(PTE_VALID)); > + > + if (encrypt) { > + clear_prot = PROT_NS_SHARED; > + ret = rsi_set_memory_range_protected(start, end); > + } else { > + set_prot = PROT_NS_SHARED; > + ret = rsi_set_memory_range_shared(start, end); > + } > + > + if (ret) > + return ret; > + > + set_prot |= PTE_VALID; > + > + return __change_memory_common(addr, PAGE_SIZE * numpages, > + __pgprot(set_prot), > + __pgprot(clear_prot)); > +} This works, does break-before-make and also rejects vmalloc() ranges (for the time being). One particular aspect I don't like is doing the TLBI twice. It's sufficient to do it when you first make the pte invalid. We could guess this in __change_memory_common() if set_mask has PTE_VALID. The call sites are restricted to this file, just add a comment. An alternative would be to add a bool flush argument to this function.
On Wed, Jun 05, 2024 at 10:30:01AM +0100, Steven Price wrote: > +static int __set_memory_encrypted(unsigned long addr, > + int numpages, > + bool encrypt) > +{ > + unsigned long set_prot = 0, clear_prot = 0; > + phys_addr_t start, end; > + int ret; > + > + if (!is_realm_world()) > + return 0; > + > + if (!__is_lm_address(addr)) > + return -EINVAL; > + > + start = __virt_to_phys(addr); > + end = start + numpages * PAGE_SIZE; > + > + /* > + * Break the mapping before we make any changes to avoid stale TLB > + * entries or Synchronous External Aborts caused by RIPAS_EMPTY > + */ > + ret = __change_memory_common(addr, PAGE_SIZE * numpages, > + __pgprot(0), > + __pgprot(PTE_VALID)); > + > + if (encrypt) { > + clear_prot = PROT_NS_SHARED; > + ret = rsi_set_memory_range_protected(start, end); > + } else { > + set_prot = PROT_NS_SHARED; > + ret = rsi_set_memory_range_shared(start, end); > + } While reading Michael's replies, it occurred to me that we need check the error paths. Here for example we ignore the return code from __change_memory_common() by overriding the 'ret' variable. I think the only other place where we don't check at all is the ITS allocation/freeing. Freeing is more interesting as I think we should not release the page back to the kernel if we did not manage to restore the original state. Better have a memory leak than data leak.
On 10/06/2024 18:27, Catalin Marinas wrote: > On Wed, Jun 05, 2024 at 10:30:01AM +0100, Steven Price wrote: >> +static int __set_memory_encrypted(unsigned long addr, >> + int numpages, >> + bool encrypt) >> +{ >> + unsigned long set_prot = 0, clear_prot = 0; >> + phys_addr_t start, end; >> + int ret; >> + >> + if (!is_realm_world()) >> + return 0; >> + >> + if (!__is_lm_address(addr)) >> + return -EINVAL; >> + >> + start = __virt_to_phys(addr); >> + end = start + numpages * PAGE_SIZE; >> + >> + /* >> + * Break the mapping before we make any changes to avoid stale TLB >> + * entries or Synchronous External Aborts caused by RIPAS_EMPTY >> + */ >> + ret = __change_memory_common(addr, PAGE_SIZE * numpages, >> + __pgprot(0), >> + __pgprot(PTE_VALID)); >> + >> + if (encrypt) { >> + clear_prot = PROT_NS_SHARED; >> + ret = rsi_set_memory_range_protected(start, end); >> + } else { >> + set_prot = PROT_NS_SHARED; >> + ret = rsi_set_memory_range_shared(start, end); >> + } >> + >> + if (ret) >> + return ret; >> + >> + set_prot |= PTE_VALID; >> + >> + return __change_memory_common(addr, PAGE_SIZE * numpages, >> + __pgprot(set_prot), >> + __pgprot(clear_prot)); >> +} > > This works, does break-before-make and also rejects vmalloc() ranges > (for the time being). > > One particular aspect I don't like is doing the TLBI twice. It's > sufficient to do it when you first make the pte invalid. We could guess > this in __change_memory_common() if set_mask has PTE_VALID. The call > sites are restricted to this file, just add a comment. An alternative > would be to add a bool flush argument to this function. > I'm always a bit scared of changing this sort of thing, but AFAICT the below should be safe: - flush_tlb_kernel_range(start, start + size); + /* + * If the memory is being made valid without changing any other bits + * then a TLBI isn't required as a non-valid entry cannot be cached in + * the TLB. + */ + if (set_mask != PTE_VALID || clear_mask) + flush_tlb_kernel_range(start, start + size); It will affect users of set_memory_valid() by removing the TLBI when marking memory as valid. I'll add this change as a separate patch so it can be reverted easily if there's something I've overlooked. Thanks, Steve
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 5d91259ee7b5..0f1480caeeec 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -20,6 +20,7 @@ config ARM64 select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE select ARCH_HAS_CACHE_LINE_SIZE + select ARCH_HAS_CC_PLATFORM select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DEBUG_VIRTUAL select ARCH_HAS_DEBUG_VM_PGTABLE @@ -41,6 +42,8 @@ config ARM64 select ARCH_HAS_SETUP_DMA_OPS select ARCH_HAS_SET_DIRECT_MAP select ARCH_HAS_SET_MEMORY + select ARCH_HAS_MEM_ENCRYPT + select ARCH_HAS_FORCE_DMA_UNENCRYPTED select ARCH_STACKWALK select ARCH_HAS_STRICT_KERNEL_RWX select ARCH_HAS_STRICT_MODULE_RWX diff --git a/arch/arm64/include/asm/mem_encrypt.h b/arch/arm64/include/asm/mem_encrypt.h new file mode 100644 index 000000000000..e47265cd180a --- /dev/null +++ b/arch/arm64/include/asm/mem_encrypt.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023 ARM Ltd. + */ + +#ifndef __ASM_MEM_ENCRYPT_H +#define __ASM_MEM_ENCRYPT_H + +#include <asm/rsi.h> + +/* All DMA must be to non-secure memory for now */ +static inline bool force_dma_unencrypted(struct device *dev) +{ + return is_realm_world(); +} + +#endif diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h index 0f740b781187..3b6619c04677 100644 --- a/arch/arm64/include/asm/set_memory.h +++ b/arch/arm64/include/asm/set_memory.h @@ -14,4 +14,7 @@ int set_direct_map_invalid_noflush(struct page *page); int set_direct_map_default_noflush(struct page *page); bool kernel_page_present(struct page *page); +int set_memory_encrypted(unsigned long addr, int numpages); +int set_memory_decrypted(unsigned long addr, int numpages); + #endif /* _ASM_ARM64_SET_MEMORY_H */ diff --git a/arch/arm64/kernel/rsi.c b/arch/arm64/kernel/rsi.c index 5cb42609219f..898952d135b0 100644 --- a/arch/arm64/kernel/rsi.c +++ b/arch/arm64/kernel/rsi.c @@ -6,6 +6,7 @@ #include <linux/jump_label.h> #include <linux/memblock.h> #include <linux/swiotlb.h> +#include <linux/cc_platform.h> #include <asm/rsi.h> @@ -19,6 +20,17 @@ unsigned int phys_mask_shift = CONFIG_ARM64_PA_BITS; DEFINE_STATIC_KEY_FALSE_RO(rsi_present); EXPORT_SYMBOL(rsi_present); +bool cc_platform_has(enum cc_attr attr) +{ + switch (attr) { + case CC_ATTR_MEM_ENCRYPT: + return is_realm_world(); + default: + return false; + } +} +EXPORT_SYMBOL_GPL(cc_platform_has); + static bool rsi_version_matches(void) { unsigned long ver_lower, ver_higher; diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index 0e270a1c51e6..3e7d81696756 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -5,10 +5,12 @@ #include <linux/kernel.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/mem_encrypt.h> #include <linux/sched.h> #include <linux/vmalloc.h> #include <asm/cacheflush.h> +#include <asm/pgtable-prot.h> #include <asm/set_memory.h> #include <asm/tlbflush.h> #include <asm/kfence.h> @@ -23,14 +25,16 @@ bool rodata_full __ro_after_init = IS_ENABLED(CONFIG_RODATA_FULL_DEFAULT_ENABLED bool can_set_direct_map(void) { /* - * rodata_full and DEBUG_PAGEALLOC require linear map to be - * mapped at page granularity, so that it is possible to + * rodata_full, DEBUG_PAGEALLOC and a Realm guest all require linear + * map to be mapped at page granularity, so that it is possible to * protect/unprotect single pages. * * KFENCE pool requires page-granular mapping if initialized late. + * + * Realms need to make pages shared/protected at page granularity. */ return rodata_full || debug_pagealloc_enabled() || - arm64_kfence_can_set_direct_map(); + arm64_kfence_can_set_direct_map() || is_realm_world(); } static int change_page_range(pte_t *ptep, unsigned long addr, void *data) @@ -192,6 +196,61 @@ int set_direct_map_default_noflush(struct page *page) PAGE_SIZE, change_page_range, &data); } +static int __set_memory_encrypted(unsigned long addr, + int numpages, + bool encrypt) +{ + unsigned long set_prot = 0, clear_prot = 0; + phys_addr_t start, end; + int ret; + + if (!is_realm_world()) + return 0; + + if (!__is_lm_address(addr)) + return -EINVAL; + + start = __virt_to_phys(addr); + end = start + numpages * PAGE_SIZE; + + /* + * Break the mapping before we make any changes to avoid stale TLB + * entries or Synchronous External Aborts caused by RIPAS_EMPTY + */ + ret = __change_memory_common(addr, PAGE_SIZE * numpages, + __pgprot(0), + __pgprot(PTE_VALID)); + + if (encrypt) { + clear_prot = PROT_NS_SHARED; + ret = rsi_set_memory_range_protected(start, end); + } else { + set_prot = PROT_NS_SHARED; + ret = rsi_set_memory_range_shared(start, end); + } + + if (ret) + return ret; + + set_prot |= PTE_VALID; + + return __change_memory_common(addr, PAGE_SIZE * numpages, + __pgprot(set_prot), + __pgprot(clear_prot)); +} + +int set_memory_encrypted(unsigned long addr, int numpages) +{ + return __set_memory_encrypted(addr, numpages, true); +} +EXPORT_SYMBOL_GPL(set_memory_encrypted); + +int set_memory_decrypted(unsigned long addr, int numpages) +{ + return __set_memory_encrypted(addr, numpages, false); +} +EXPORT_SYMBOL_GPL(set_memory_decrypted); + #ifdef CONFIG_DEBUG_PAGEALLOC void __kernel_map_pages(struct page *page, int numpages, int enable) {