This series is a follow-up to the discussion that has started here [1].
When memory is lent to the Secure world via FF-A, CPU speculative accesses from NS to the lent pages can still occur as long as it retains a cacheable mapping to it.
Ideally, lent memory would be "no-map" but that would mean giving up MiBs of useful memory, so let's try to do better with the help of a CMA pool.
On arm64, modifying the direct map at runtime is generally restricted because the linear map defaults to block mapping and splitting blocks at runtime may trigger fatal page fault, unless the CPU implements BBML3 or the entire direct map was mapped at page granularity from boot. Forcing last-level mappings system-wide incurs a severe penalty we want to avoid. Instead, this series introduces targeted last-level mappings for designated memory regions, along with a generic "lendable-dma-pool" CMA driver to manage unmapping and remapping on lend/reclaim transitions:
1. memblock: - Introduce MEMBLOCK_PTEMAP to force PTE mappings only for a specific region.
2. dma-mapping: - Introduce the "lendable-dma-pool" CMA reserved-memory driver, along with generic dma_zap_direct_map() and dma_restore_direct_map() APIs to unmap pages from the direct map and restore them.
3. arm64 direct map support: - Implement the "lendable-dma-pool" hooks.
4. OP-TEE support: - Hook OP-TEE dynamic protected memory allocation (tee_shm_alloc_dma_mem()) to dma_zap_direct_map() and dma_restore_direct_map().
Testing: ========
Tested with QEMU v8 using OP-TEE OS (built with CFG_CORE_DYN_PROTMEM=y) under both SMC and FF-A transports [2]
static void dump_direct_map(const char *label) { printf("\n=== %s ===\n", label); fflush(stdout); system("sed -n '/Linear Mapping start/,/Linear Mapping end/p' /sys/kernel/debug/kernel_page_tables"); fflush(stdout); }
int main(int argc, char *argv[]) { int heap_fd; int dmabuf_fd; struct dma_heap_allocation_data data = { 0 }; size_t size = 1024 * 1024; /* 1MB */
if (argc > 1) size = strtoul(argv[1], NULL, 0);
dump_direct_map("BEFORE ALLOCATION");
heap_fd = open("/dev/dma_heap/protected,secure-video", O_RDWR); if (heap_fd < 0) { perror("open /dev/dma_heap/protected,secure-video"); return 1; }
printf("\nOpened /dev/dma_heap/protected,secure-video\n"); printf("Allocating %zu bytes of protected memory via DMA heap...\n", size);
data.len = size; data.fd_flags = O_RDWR | O_CLOEXEC; if (ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) { perror("ioctl DMA_HEAP_IOCTL_ALLOC"); close(heap_fd); return 1; }
dmabuf_fd = data.fd; printf("Successfully allocated %zu bytes! dmabuf_fd = %d\n", size, dmabuf_fd);
dump_direct_map("DURING LEND (EXPECT HOLE IN DIRECT MAP)");
printf("\nReleasing dmabuf_fd...\n"); close(dmabuf_fd); close(heap_fd);
dump_direct_map("AFTER RECLAIM (RESTORED DIRECT MAP)");
return 0; }
[1] https://lore.kernel.org/all/20260807-tegra-vpr-v4-7-5510d16af89e@nvidia.com/ [2] https://optee.readthedocs.io/en/latest/building/gits/build.html#qemu-v8
Changelog:
v3: - Replace "arm,ffa-lend-pool" and OP-TEE "memory-region" DT binding with generic "lendable-dma-pool" compatible with "linux,cma-default" (Sumit). - move conflict check to flag-setting time in memblock_setclr_flag() (Mike) - Rebase onto v7.4-rc1.
v2 (https://lore.kernel.org/all/20260921110050.3977591-1-vdonnefort@google.com/): - Rename MEMBLOCK_LLMAP to MEMBLOCK_PTEMAP (Mike) - Warn on conflicting MEMBLOCK_NOMAP and MEMBLOCK_PTEMAP flags (Mike) - Drop DT "ll-map" property and mark MEMBLOCK_PTEMAP from ffa_lend_pool_setup() (Rob, Thierry) - Drop "reusable" and "no-map" property checks from ffa_lend_pool_setup() (Rob) - dt-bindings: Document arm,ffa-lend-pool (Rob) - Rework the Optee support with a separate ffa_lend_pool.c file. - use walk_kernel_page_table_range_lockless() for can_set_direct_map_range() to fix folded page table (Sashiko) - Add missing TLB flush in ffa_prepare_lend() - Disable hibernation - Rebase on linux-next (next-20260918) to use the new set_direct_map*() ranges (Mike)
v1 (https://lore.kernel.org/all/20260902104712.2399797-1-vdonnefort@google.com/)
Vincent Donnefort (8): memblock: Introduce MEMBLOCK_PTEMAP arm64: Add support for MEMBLOCK_PTEMAP dma-contiguous: Expose rmem_cma_ops dt-bindings: reserved-memory: Add lendable-dma-pool dma-mapping: Introduce lendable-dma-pool arm64: Introduce direct_map_is_ptemap() arm64: Add support for lendable-dma-pool optee: Add support for lendable-dma-pool
.../reserved-memory/lendable-dma-pool.yaml | 58 +++++ arch/arm64/Kconfig | 1 + arch/arm64/include/asm/set_memory.h | 6 + arch/arm64/mm/dma-mapping.c | 71 +++++ arch/arm64/mm/mmu.c | 22 +- arch/arm64/mm/pageattr.c | 121 ++++++++- drivers/tee/optee/protmem.c | 48 +++- include/linux/dma-map-ops.h | 9 + include/linux/dma-mapping.h | 16 ++ include/linux/memblock.h | 9 + kernel/dma/Kconfig | 3 + kernel/dma/Makefile | 1 + kernel/dma/contiguous.c | 4 +- kernel/dma/contiguous.h | 10 + kernel/dma/lendable.c | 243 ++++++++++++++++++ mm/memblock.c | 39 ++- 16 files changed, 630 insertions(+), 31 deletions(-) create mode 100644 Documentation/devicetree/bindings/reserved-memory/lendable-dma-pool.yaml create mode 100644 kernel/dma/contiguous.h create mode 100644 kernel/dma/lendable.c
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
Keeping PTE-level mappings is interesting for arm64 as it allows mapping/unmapping pages from the kernel direct map without the risk of splitting blocks which, under the break-before-make rule, may trigger page-faults the kernel can't handle.
However, mapping the entire direct map at PTE-level is costly. So instead, create a new memblock flag MEMBLOCK_PTEMAP to selectively apply a PTE-level mapping.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- include/linux/memblock.h | 9 +++++++++ mm/memblock.c | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/include/linux/memblock.h b/include/linux/memblock.h index 40e6e0aa2f8d..67ef20a1c5e2 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -52,6 +52,7 @@ extern unsigned long long max_possible_pfn; * know is good to use. It is the only memory that allocations may happen from * in this phase. * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages + * @MEMBLOCK_PTEMAP: memory region to be mapped using PTE-level mapping */ enum memblock_flags { MEMBLOCK_NONE = 0x0, /* No special request */ @@ -63,6 +64,7 @@ enum memblock_flags { MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */ MEMBLOCK_KHO_NOPRSRV = 0x40, /* memory with no KHO preservations */ MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */ + MEMBLOCK_PTEMAP = 0x100,/* PTE-level mapping */ };
/** @@ -160,6 +162,8 @@ int memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size); int memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t size); int memblock_mark_kho_noprsrv(phys_addr_t base, phys_addr_t size); int memblock_clear_kho_noprsrv(phys_addr_t base, phys_addr_t size); +int memblock_mark_ptemap(phys_addr_t base, phys_addr_t size); +int memblock_clear_ptemap(phys_addr_t base, phys_addr_t size);
void memblock_free(void *ptr, size_t size);
@@ -305,6 +309,11 @@ static inline bool memblock_is_kho_noprsrv(struct memblock_region *m) return m->flags & MEMBLOCK_KHO_NOPRSRV; }
+static inline bool memblock_is_ptemap(struct memblock_region *m) +{ + return m->flags & MEMBLOCK_PTEMAP; +} + int memblock_search_pfn_nid(unsigned long pfn, unsigned long *start_pfn, unsigned long *end_pfn); void __next_mem_pfn_range(int *idx, int nid, unsigned long *out_start_pfn, diff --git a/mm/memblock.c b/mm/memblock.c index a450092a80fb..b8b6e762917a 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -1068,10 +1068,15 @@ static int __init_memblock memblock_setclr_flag(struct memblock_type *type, for (i = start_rgn; i < end_rgn; i++) { struct memblock_region *r = &type->regions[i];
- if (set) + if (set) { + enum memblock_flags map_flags = + MEMBLOCK_NOMAP | MEMBLOCK_PTEMAP; + r->flags |= flag; - else + WARN_ON_ONCE((r->flags & map_flags) == map_flags); + } else { r->flags &= ~flag; + } }
memblock_merge_regions(type, start_rgn, end_rgn); @@ -1221,6 +1226,35 @@ __init int memblock_clear_kho_noprsrv(phys_addr_t base, phys_addr_t size) MEMBLOCK_KHO_NOPRSRV); }
+/** + * memblock_mark_ptemap - Mark a memory region with flag MEMBLOCK_PTEMAP. + * @base: the base phys addr of the region + * @size: the size of the region + * + * If supported by the architecture, such region is mapped using PTE-level + * mappings in the kernel direct map. + * + * Return: 0 on success, -errno on failure. + */ +int __init_memblock memblock_mark_ptemap(phys_addr_t base, phys_addr_t size) +{ + return memblock_setclr_flag(&memblock.memory, base, size, 1, + MEMBLOCK_PTEMAP); +} + +/** + * memblock_clear_ptemap - Clear flag MEMBLOCK_PTEMAP for a specified region. + * @base: the base phys addr of the region + * @size: the size of the region + * + * Return: 0 on success, -errno on failure. + */ +int __init_memblock memblock_clear_ptemap(phys_addr_t base, phys_addr_t size) +{ + return memblock_setclr_flag(&memblock.memory, base, size, 0, + MEMBLOCK_PTEMAP); +} + static bool should_skip_region(struct memblock_type *type, struct memblock_region *m, int nid, int flags) @@ -2854,6 +2888,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", [ilog2(MEMBLOCK_KHO_NOPRSRV)] = "KHO_NOPRSRV", [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", + [ilog2(MEMBLOCK_PTEMAP)] = "PTEMAP", };
static int memblock_debug_show(struct seq_file *m, void *private)
With the MEMBLOCK_PTEMAP flag, force a PTE-level mapping. This also disables contiguous PTEs to make sure any page from the memblock can be safely unmapped from the linear map.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- arch/arm64/mm/mmu.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 140520b351e2..a4cafafa1d82 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -1184,9 +1184,8 @@ static void __init map_mem(void) phys_addr_t init_begin = __pa_symbol(__init_begin); phys_addr_t init_end = __pa_symbol(__init_end); phys_addr_t kernel_end = __pa_symbol(__bss_stop); - phys_addr_t start, end; int flags = NO_EXEC_MAPPINGS; - u64 i; + struct memblock_region *r;
/* * Setting hierarchical PXNTable attributes on table entries covering @@ -1226,9 +1225,14 @@ static void __init map_mem(void) flags);
/* map all the memory banks */ - for_each_mem_range(i, &start, &end) { + for_each_mem_region(r) { + phys_addr_t start, end; + int rflags = flags; + + if (memblock_is_nomap(r)) + continue; /* - * for_each_mem_range may return sub-page-aligned boundaries + * for_each_mem_region may return sub-page-aligned boundaries * after memblock_mark_nomap() splits regions at byte precision. * __create_pgd_mapping_locked aligns phys down to PAGE_MASK, * which could accidentally map no-map memory on the boundary. @@ -1237,17 +1241,21 @@ static void __init map_mem(void) * regions. The cost is at most one page of unmapped gap at * each boundary. */ - start = PAGE_ALIGN(start); - end = end & PAGE_MASK; + start = PAGE_ALIGN(r->base); + end = (r->base + r->size) & PAGE_MASK; if (start >= end) continue; + + if (memblock_is_ptemap(r)) + rflags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS; + /* * The linear map must allow allocation tags reading/writing * if MTE is present. Otherwise, it has the same attributes as * PAGE_KERNEL. */ __map_memblock(start, end, pgprot_tagged(PAGE_KERNEL), - flags); + rflags); } }
Expose rmem_cma_ops via a kernel/dma-private header so the upcoming lendable-dma-pool reserved-memory handler can wrap the CMA callbacks instead of duplicating them.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- kernel/dma/contiguous.c | 4 +++- kernel/dma/contiguous.h | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 kernel/dma/contiguous.h
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c index 90937dc7c627..22d6e54aea35 100644 --- a/kernel/dma/contiguous.c +++ b/kernel/dma/contiguous.c @@ -46,6 +46,8 @@ #include <linux/cma.h> #include <linux/nospec.h>
+#include "contiguous.h" + #ifdef CONFIG_CMA_SIZE_MBYTES #define CMA_SIZE_MBYTES CONFIG_CMA_SIZE_MBYTES #else @@ -578,7 +580,7 @@ static int __init rmem_cma_setup(unsigned long node, struct reserved_mem *rmem) return 0; }
-static const struct reserved_mem_ops rmem_cma_ops = { +const struct reserved_mem_ops rmem_cma_ops = { .node_validate = rmem_cma_validate, .node_fixup = rmem_cma_fixup, .node_init = rmem_cma_setup, diff --git a/kernel/dma/contiguous.h b/kernel/dma/contiguous.h new file mode 100644 index 000000000000..9b1fdbfc794f --- /dev/null +++ b/kernel/dma/contiguous.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _KERNEL_DMA_CONTIGUOUS_H +#define _KERNEL_DMA_CONTIGUOUS_H + +#ifdef CONFIG_OF_RESERVED_MEM +struct reserved_mem_ops; +extern const struct reserved_mem_ops rmem_cma_ops; +#endif + +#endif /* _KERNEL_DMA_CONTIGUOUS_H */
Add bindings for the "lendable-dma-pool" reserved-memory CMA pool, used to dynamically unmap pages from the kernel direct map before lending them to a secure world.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- .../reserved-memory/lendable-dma-pool.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/reserved-memory/lendable-dma-pool.yaml
diff --git a/Documentation/devicetree/bindings/reserved-memory/lendable-dma-pool.yaml b/Documentation/devicetree/bindings/reserved-memory/lendable-dma-pool.yaml new file mode 100644 index 000000000000..0b924d941b4d --- /dev/null +++ b/Documentation/devicetree/bindings/reserved-memory/lendable-dma-pool.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reserved-memory/lendable-dma-pool.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: /reserved-memory Lendable DMA Pool + +maintainers: + - Vincent Donnefort vdonnefort@google.com + +description: | + This indicates a region of memory meant to be used as a pool of DMA buffers + that can be dynamically unmapped from the kernel direct map (e.g. before + lending to a secure world such as via Arm FF-A). + + On architectures where the direct map cannot be arbitrarily modified at + runtime (such as arm64 without BBML3 or without rodata=full), the operating + system ensures this pool is mapped using last-level (PTE) page table entries. + + The reusable property must be set, and no-map must not be set. + +allOf: + - $ref: reserved-memory.yaml + +properties: + compatible: + const: lendable-dma-pool + + linux,cma-default: + type: boolean + description: > + If this property is present, then Linux will use the region for + the default pool of the contiguous memory allocator. + + no-map: false + +required: + - compatible + - reusable + +unevaluatedProperties: false + +examples: + - | + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + linux,cma { + compatible = "lendable-dma-pool"; + reusable; + size = <0x4000000>; + alignment = <0x200000>; + linux,cma-default; + }; + };
On arm64, when memory is lent to secure world (e.g. via Arm FF-A), fatal CPU speculative reads can still occur while it retains a cacheable direct mapping. Introduce the "lendable-dma-pool" reserved-memory CMA driver with dma_zap_direct_map() and dma_restore_direct_map() to unmap pages before lending and restore them upon reclaim. Devices use the pool via "memory-region" or "linux,cma-default":
reserved-memory { #address-cells = <0x2>; #size-cells = <0x2>; ranges;
linux,cma { compatible = "lendable-dma-pool"; reusable; linux,cma-default; size = <0x0 0x4000000>; }; };
If no lendable-dma-pool node is provided, the API falls back to can_set_direct_map().
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- include/linux/dma-map-ops.h | 9 ++ include/linux/dma-mapping.h | 16 +++ kernel/dma/Kconfig | 3 + kernel/dma/Makefile | 1 + kernel/dma/lendable.c | 243 ++++++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+) create mode 100644 kernel/dma/lendable.c
diff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h index afaf2c12189b..e1d1e8d70855 100644 --- a/include/linux/dma-map-ops.h +++ b/include/linux/dma-map-ops.h @@ -379,6 +379,15 @@ static inline void arch_dma_prep_coherent(struct page *page, size_t size) } #endif /* CONFIG_ARCH_HAS_DMA_PREP_COHERENT */
+#ifdef CONFIG_DMA_LENDABLE +int __init arch_dma_mark_lendable(phys_addr_t base, phys_addr_t size); +bool __init arch_dma_is_lendable(struct page *page, unsigned int nr_pages); +int arch_dma_zap_direct_map(struct page *page, unsigned int nr_pages, + bool from_lendable); +int arch_dma_restore_direct_map(struct page *page, unsigned int nr_pages, + bool from_lendable); +#endif /* CONFIG_DMA_LENDABLE */ + void *arch_dma_set_uncached(void *addr, size_t size); void arch_dma_clear_uncached(void *addr, size_t size);
diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h index efde0e5a58dc..24239599505d 100644 --- a/include/linux/dma-mapping.h +++ b/include/linux/dma-mapping.h @@ -511,6 +511,22 @@ void dma_free_pages(struct device *dev, size_t size, struct page *page, int dma_mmap_pages(struct device *dev, struct vm_area_struct *vma, size_t size, struct page *page);
+#ifdef CONFIG_DMA_LENDABLE +int dma_zap_direct_map(struct device *dev, struct page *page, size_t size); +void dma_restore_direct_map(struct device *dev, struct page *page, size_t size); +#else +static inline int dma_zap_direct_map(struct device *dev, struct page *page, + size_t size) +{ + return -ENODEV; +} + +static inline void dma_restore_direct_map(struct device *dev, struct page *page, + size_t size) +{ +} +#endif /* CONFIG_DMA_LENDABLE */ + static inline void *dma_alloc_noncoherent(struct device *dev, size_t size, dma_addr_t *dma_handle, enum dma_data_direction dir, gfp_t gfp) { diff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig index 3830a63ae032..8d65e2daa7d2 100644 --- a/kernel/dma/Kconfig +++ b/kernel/dma/Kconfig @@ -192,6 +192,9 @@ config DMA_CMA
if DMA_CMA
+config DMA_LENDABLE + bool + config DMA_NUMA_CMA bool "Enable separate DMA Contiguous Memory Area for NUMA Node" depends on NUMA diff --git a/kernel/dma/Makefile b/kernel/dma/Makefile index 6977033444a3..6b257d82bd9b 100644 --- a/kernel/dma/Makefile +++ b/kernel/dma/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_HAS_DMA) += mapping.o direct.o obj-$(CONFIG_DMA_OPS_HELPERS) += ops_helpers.o obj-$(CONFIG_ARCH_HAS_DMA_OPS) += dummy.o obj-$(CONFIG_DMA_CMA) += contiguous.o +obj-$(CONFIG_DMA_LENDABLE) += lendable.o obj-$(CONFIG_DMA_DECLARE_COHERENT) += coherent.o obj-$(CONFIG_DMA_API_DEBUG) += debug.o obj-$(CONFIG_SWIOTLB) += swiotlb.o diff --git a/kernel/dma/lendable.c b/kernel/dma/lendable.c new file mode 100644 index 000000000000..85ac3ad10df0 --- /dev/null +++ b/kernel/dma/lendable.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Lendable DMA Contiguous Memory Pool + * + * Prevents CPU speculative reads to DMA memory (e.g. when lent to a secure + * world) by unmapping it from the kernel direct map. This works if the + * architecture allows modifying the direct map globally (can_set_direct_map()) + * or if the memory belongs to a reserved-memory CMA pool compatible with + * "lendable-dma-pool". + * + * Copyright (C) 2026 Google LLC + * Author: Vincent Donnefort vdonnefort@google.com + */ + +#define pr_fmt(fmt) "dma-lendable: " fmt + +#include <linux/atomic.h> +#include <linux/bsearch.h> +#include <linux/cma.h> +#include <linux/dma-map-ops.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/of_reserved_mem.h> +#include <linux/set_memory.h> +#include <linux/sort.h> +#include <linux/suspend.h> + +#include "contiguous.h" + +#define DMA_LENDABLE_DISABLED -1 + +static struct cma *lendable_areas[MAX_CMA_AREAS] __ro_after_init; +static unsigned int nr_lendable_areas __ro_after_init; +static atomic_t dma_lendable_active; + +static int __lendable_cmp(const void *a, const void *b) +{ + const struct cma *cma_a = *(struct cma * const *)a; + const struct cma *cma_b = *(struct cma * const *)b; + + return cmp_int(cma_get_base(cma_a), cma_get_base(cma_b)); +} + +static bool cma_is_lendable(struct cma *cma) +{ + return cma && bsearch(&cma, lendable_areas, nr_lendable_areas, + sizeof(lendable_areas[0]), __lendable_cmp); +} + +static bool cma_has_range(struct cma *cma, struct page *page, unsigned int nr_pages) +{ + phys_addr_t start = page_to_phys(page); + phys_addr_t end = start + ((phys_addr_t)nr_pages << PAGE_SHIFT); + phys_addr_t base = cma_get_base(cma); + + return start >= base && end <= base + cma_get_size(cma); +} + +/** + * dma_zap_direct_map() - Remove a DMA page range from the kernel direct map + * @dev: Device that allocated the pages (or NULL for default CMA pool) + * @page: First page of the memory region + * @size: Page-aligned size of the memory region in bytes + * + * Cleans and invalidates caches and removes the memory range from the kernel + * direct map to prevent speculative CPU accesses. Must be paired with + * dma_restore_direct_map(). + * + * Context: Any context if allocated from a lendable pool; process context + * (may sleep) otherwise. + * Return: + * * 0 - Success. + * * -EINVAL - @size is 0 or not page-aligned. + * * -ENODEV - Direct map cannot be modified and @dev has no lendable pool. + * * -EFAULT - @dev has a lendable pool, but [@page, @page + @size) is + * outside that pool. + * * -EBUSY - Direct map zapping is temporarily disabled (hibernation). + */ +int dma_zap_direct_map(struct device *dev, struct page *page, size_t size) +{ + unsigned int nr_pages = size >> PAGE_SHIFT; + bool from_lendable, has_lendable; + struct cma *cma; + int ret; + + if (!size || !PAGE_ALIGNED(size)) + return -EINVAL; + + cma = dev_get_cma_area(dev); + has_lendable = cma_is_lendable(cma); + from_lendable = has_lendable && cma_has_range(cma, page, nr_pages); + + if (!from_lendable && !can_set_direct_map()) + return has_lendable ? -EFAULT : -ENODEV; + + /* Provides full ordering */ + if (!atomic_add_unless(&dma_lendable_active, 1, DMA_LENDABLE_DISABLED)) + return -EBUSY; + + ret = arch_dma_zap_direct_map(page, nr_pages, from_lendable); + if (ret) + atomic_dec(&dma_lendable_active); + + return ret; +} +EXPORT_SYMBOL_GPL(dma_zap_direct_map); + +/** + * dma_restore_direct_map() - Restore a zapped DMA page range in the direct map + * @dev: Device that allocated the pages (or NULL for default CMA pool) + * @page: First page of the memory region + * @size: Page-aligned size of the memory region in bytes + * + * Restores the default kernel direct mapping and invalidates caches for a range + * previously zapped with dma_zap_direct_map(). + * + * Context: Any context if allocated from a lendable pool; process context + * (may sleep) otherwise. + */ +void dma_restore_direct_map(struct device *dev, struct page *page, size_t size) +{ + unsigned int nr_pages = size >> PAGE_SHIFT; + bool from_lendable; + struct cma *cma; + + if (WARN_ON_ONCE(!size || !PAGE_ALIGNED(size))) + return; + + cma = dev_get_cma_area(dev); + from_lendable = cma_is_lendable(cma) && cma_has_range(cma, page, nr_pages); + + if (WARN_ON_ONCE(!from_lendable && !can_set_direct_map())) + return; + + if (WARN_ON_ONCE(arch_dma_restore_direct_map(page, nr_pages, from_lendable))) + return; + + WARN_ON_ONCE(atomic_dec_return_release(&dma_lendable_active) < 0); +} +EXPORT_SYMBOL_GPL(dma_restore_direct_map); + +static int dma_lendable_pm_notify(struct notifier_block *nb, + unsigned long mode, void *data) +{ + /* Prevent hibernation while unmapped pages may be inaccessible */ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + if (atomic_cmpxchg_acquire(&dma_lendable_active, 0, DMA_LENDABLE_DISABLED)) + return notifier_from_errno(-EBUSY); + break; + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + atomic_cmpxchg(&dma_lendable_active, DMA_LENDABLE_DISABLED, 0); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block dma_lendable_pm_nb = { + .notifier_call = dma_lendable_pm_notify, +}; + +static int __init dma_lendable_init(void) +{ + unsigned int i, nr = 0; + + for (i = 0; i < nr_lendable_areas; i++) { + struct cma *cma = lendable_areas[i]; + + if (!arch_dma_is_lendable(phys_to_page(cma_get_base(cma)), + cma_get_size(cma) >> PAGE_SHIFT)) { + pr_warn("Ignoring %s: not lendable\n", cma_get_name(cma)); + continue; + } + + lendable_areas[nr++] = cma; + } + nr_lendable_areas = nr; + + sort(lendable_areas, nr_lendable_areas, sizeof(lendable_areas[0]), __lendable_cmp, NULL); + + register_pm_notifier(&dma_lendable_pm_nb); + + return 0; +} +core_initcall(dma_lendable_init); + +#ifdef CONFIG_OF_RESERVED_MEM +static int __init rmem_lendable_validate(unsigned long node, + phys_addr_t *align) +{ + return rmem_cma_ops.node_validate(node, align); +} + +static int __init rmem_lendable_fixup(unsigned long node, phys_addr_t base, + phys_addr_t size) +{ + int ret = rmem_cma_ops.node_fixup(node, base, size); + + if (ret) + return ret; + + return arch_dma_mark_lendable(base, size); +} + +static int __init rmem_lendable_setup(unsigned long node, + struct reserved_mem *rmem) +{ + int ret = rmem_cma_ops.node_init(node, rmem); + + if (ret) + return ret; + + lendable_areas[nr_lendable_areas++] = rmem->priv; + + return 0; +} + +static int rmem_lendable_device_init(struct reserved_mem *rmem, + struct device *dev) +{ + return rmem_cma_ops.device_init(rmem, dev); +} + +static void rmem_lendable_device_release(struct reserved_mem *rmem, + struct device *dev) +{ + rmem_cma_ops.device_release(rmem, dev); +} + +static const struct reserved_mem_ops rmem_lendable_ops = { + .node_validate = rmem_lendable_validate, + .node_fixup = rmem_lendable_fixup, + .node_init = rmem_lendable_setup, + .device_init = rmem_lendable_device_init, + .device_release = rmem_lendable_device_release, +}; + +RESERVEDMEM_OF_DECLARE(cma_lendable, "lendable-dma-pool", &rmem_lendable_ops); +#endif /* CONFIG_OF_RESERVED_MEM */
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Medium] dma-mapping: linux,cma-default lendable pool bypassed in dma_alloc_contiguous() --
commit b8e3981ec38ba68b29bb5ed6b932c6f83a300db4 Author: Vincent Donnefort vdonnefort@google.com
dma-mapping: Introduce lendable-dma-pool
This commit introduces the "lendable-dma-pool" reserved-memory CMA driver along with dma_zap_direct_map() and dma_restore_direct_map() to unmap DMA pages from the kernel direct map before lending them to a secure world and restore them upon reclaim.
diff --git a/kernel/dma/lendable.c b/kernel/dma/lendable.c new file mode 100644 index 0000000000000..85ac3ad10df03 --- /dev/null +++ b/kernel/dma/lendable.c @@ -0,0 +1,243 @@
[ ... ]
+int dma_zap_direct_map(struct device *dev, struct page *page, size_t size) +{
- unsigned int nr_pages = size >> PAGE_SHIFT;
- bool from_lendable, has_lendable;
- struct cma *cma;
- int ret;
- if (!size || !PAGE_ALIGNED(size))
return -EINVAL;- cma = dev_get_cma_area(dev);
- has_lendable = cma_is_lendable(cma);
- from_lendable = has_lendable && cma_has_range(cma, page, nr_pages);
- if (!from_lendable && !can_set_direct_map())
return has_lendable ? -EFAULT : -ENODEV;
[Severity: Medium] Can dma_zap_direct_map() fail with -EFAULT for pages allocated via dma_alloc_pages() or dma_alloc_coherent() when a lendable pool is configured as "linux,cma-default" without a per-device memory-region?
When dev->cma_area is NULL, dev_get_cma_area(dev) returns dma_contiguous_default_area, so has_lendable is true. However, dma_alloc_contiguous() skips dma_contiguous_default_area for single-page allocations (returning NULL so the caller falls back to the buddy allocator) and prefers dma_contiguous_numa_area[nid] when CONFIG_DMA_NUMA_CMA is enabled:
kernel/dma/contiguous.c:dma_alloc_contiguous() { ... if (dev->cma_area) return cma_alloc_aligned(dev->cma_area, size, gfp); if (size <= PAGE_SIZE) return NULL;
#ifdef CONFIG_DMA_NUMA_CMA if (nid != NUMA_NO_NODE && !(gfp & (GFP_DMA | GFP_DMA32))) { struct cma *cma = dma_contiguous_numa_area[nid]; ... }
As a result, those pages do not come from dma_contiguous_default_area, cma_has_range() returns false, and dma_zap_direct_map() returns -EFAULT whenever can_set_direct_map() is false.
Regions marked with MEMBLOCK_PTEMAP are mapped at page granularity so their direct mapping can be modified without splitting blocks at runtime. Introduce direct_map_is_ptemap(), which walks the kernel page tables during init to verify that every page in a range is backed by a present non-contiguous PTE.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- arch/arm64/include/asm/set_memory.h | 4 ++ arch/arm64/mm/pageattr.c | 65 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+)
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h index 0091ba12200e..b6ec938703d6 100644 --- a/arch/arm64/include/asm/set_memory.h +++ b/arch/arm64/include/asm/set_memory.h @@ -3,12 +3,16 @@ #ifndef _ASM_ARM64_SET_MEMORY_H #define _ASM_ARM64_SET_MEMORY_H
+#include <linux/init.h> + #include <asm/mem_encrypt.h> #include <asm-generic/set_memory.h>
bool can_set_direct_map(void); #define can_set_direct_map can_set_direct_map
+bool __init direct_map_is_ptemap(struct page *page, unsigned int numpages); + int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page, unsigned int numpages); diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index de7bf95003ea..c7662fd2fcb2 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -102,6 +102,71 @@ bool can_set_direct_map(void) arm64_kfence_can_set_direct_map() || is_realm_world(); }
+static int __init direct_map_is_ptemap_pud_entry(pud_t *pud, unsigned long addr, + unsigned long next, + struct mm_walk *walk) +{ + if (pud_leaf(pudp_get(pud))) + return -EINVAL; + + return 0; +} + +static int __init direct_map_is_ptemap_pmd_entry(pmd_t *pmd, unsigned long addr, + unsigned long next, + struct mm_walk *walk) +{ + if (pmd_leaf(pmdp_get(pmd))) + return -EINVAL; + + return 0; +} + +static int __init direct_map_is_ptemap_pte_entry(pte_t *pte, unsigned long addr, + unsigned long next, + struct mm_walk *walk) +{ + pte_t val = __ptep_get(pte); + + if (!pte_present(val) || pte_cont(val)) + return -EINVAL; + + return 0; +} + +static int __init direct_map_is_ptemap_pte_hole(unsigned long addr, + unsigned long next, int depth, + struct mm_walk *walk) +{ + return -EINVAL; +} + +static const struct mm_walk_ops direct_map_is_ptemap_ops __initconst = { + .pud_entry = direct_map_is_ptemap_pud_entry, + .pmd_entry = direct_map_is_ptemap_pmd_entry, + .pte_entry = direct_map_is_ptemap_pte_entry, + .pte_hole = direct_map_is_ptemap_pte_hole, +}; + +bool __init direct_map_is_ptemap(struct page *page, unsigned int numpages) +{ + unsigned long addr, end; + + if (!pfn_valid(page_to_pfn(page)) || !numpages) + return false; + + addr = (unsigned long)page_address(page); + end = addr + PAGE_SIZE * numpages; + + /* + * Called during boot before runtime linear-map splitting, so it is + * safe to walk the page-table lockless. + */ + return !walk_kernel_page_table_range_lockless(addr, end, + &direct_map_is_ptemap_ops, + NULL, NULL); +} + static int update_range_prot(unsigned long start, unsigned long size, pgprot_t set_mask, pgprot_t clear_mask) {
Even when the linear map uses block mappings, regions marked with MEMBLOCK_PTEMAP are mapped at page granularity and can be modified at runtime. Implement the architecture hooks for the lendable-dma-pool to force the last-level mappings, validate ranges and update the direct mapping.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/set_memory.h | 2 + arch/arm64/mm/dma-mapping.c | 71 +++++++++++++++++++++++++++++ arch/arm64/mm/pageattr.c | 56 ++++++++++++++++++----- 4 files changed, 118 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1075bc661833..34fe7a7eea82 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -117,6 +117,7 @@ config ARM64 select DYNAMIC_FTRACE if FUNCTION_TRACER select DMA_BOUNCE_UNALIGNED_KMALLOC select DMA_DIRECT_REMAP + select DMA_LENDABLE if DMA_CMA select EDAC_SUPPORT select FRAME_POINTER select FUNCTION_ALIGNMENT_4B diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h index b6ec938703d6..ec8bbb59e51f 100644 --- a/arch/arm64/include/asm/set_memory.h +++ b/arch/arm64/include/asm/set_memory.h @@ -17,6 +17,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page, unsigned int numpages); int set_direct_map_default_noflush(struct page *page, unsigned int numpages); +int __set_direct_map_invalid_noflush(struct page *page, unsigned int numpages); +int __set_direct_map_default_noflush(struct page *page, unsigned int numpages); bool kernel_page_present(struct page *page);
int set_memory_encrypted(unsigned long addr, int numpages); diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index 7f6f2f40dba2..c324e9826ce2 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -7,9 +7,13 @@ #include <linux/gfp.h> #include <linux/cache.h> #include <linux/dma-map-ops.h> +#include <linux/kernel.h> +#include <linux/memblock.h> #include <xen/xen.h>
#include <asm/cacheflush.h> +#include <asm/set_memory.h> +#include <asm/tlbflush.h> #include <asm/xen/xen-ops.h>
void arch_sync_dma_for_device(phys_addr_t paddr, size_t size, @@ -38,6 +42,73 @@ void arch_dma_prep_coherent(struct page *page, size_t size) dcache_clean_poc(start, start + size); }
+#ifdef CONFIG_DMA_LENDABLE +int __init arch_dma_mark_lendable(phys_addr_t base, phys_addr_t size) +{ + if (!PAGE_ALIGNED(base | size)) + return -EINVAL; + + return memblock_mark_ptemap(base, size); +} + +bool __init arch_dma_is_lendable(struct page *page, unsigned int nr_pages) +{ + return direct_map_is_ptemap(page, nr_pages); +} + +int arch_dma_zap_direct_map(struct page *page, unsigned int nr_pages, + bool from_lendable) +{ + unsigned long start = (unsigned long)page_address(page); + unsigned long end = start + ((unsigned long)nr_pages << PAGE_SHIFT); + int ret; + + /* + * arch_dma_prep_coherent() only cleans as normal DMA buffers retain a + * cacheable linear-map alias. Since that alias is being removed here, + * also invalidate to make sure they can't be evicted later on while the + * memory is secured. + */ + dcache_clean_inval_poc(start, end); + + if (from_lendable) { + ret = __set_direct_map_invalid_noflush(page, nr_pages); + } else { + might_sleep(); + ret = set_direct_map_invalid_noflush(page, nr_pages); + } + + if (ret) + return ret; + + flush_tlb_kernel_range(start, end); + + return 0; +} + +int arch_dma_restore_direct_map(struct page *page, unsigned int nr_pages, + bool from_lendable) +{ + unsigned long start = (unsigned long)page_address(page); + unsigned long end = start + ((unsigned long)nr_pages << PAGE_SHIFT); + int ret; + + if (from_lendable) { + ret = __set_direct_map_default_noflush(page, nr_pages); + } else { + might_sleep(); + ret = set_direct_map_default_noflush(page, nr_pages); + } + + if (ret) + return ret; + + dcache_inval_poc(start, end); + + return 0; +} +#endif /* CONFIG_DMA_LENDABLE */ + void arch_setup_dma_ops(struct device *dev, bool coherent) { int cls = cache_line_size_of_cpu(); diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index c7662fd2fcb2..467a420af0f5 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -167,8 +167,8 @@ bool __init direct_map_is_ptemap(struct page *page, unsigned int numpages) NULL, NULL); }
-static int update_range_prot(unsigned long start, unsigned long size, - pgprot_t set_mask, pgprot_t clear_mask) +static int __update_range_prot(unsigned long start, unsigned long size, + pgprot_t set_mask, pgprot_t clear_mask) { struct page_change_data data; int ret; @@ -176,10 +176,6 @@ static int update_range_prot(unsigned long start, unsigned long size, data.set_mask = set_mask; data.clear_mask = clear_mask;
- ret = split_kernel_leaf_mapping(start, start + size); - if (WARN_ON_ONCE(ret)) - return ret; - lazy_mmu_mode_enable();
/* @@ -194,6 +190,18 @@ static int update_range_prot(unsigned long start, unsigned long size, return ret; }
+static int update_range_prot(unsigned long start, unsigned long size, + pgprot_t set_mask, pgprot_t clear_mask) +{ + int ret; + + ret = split_kernel_leaf_mapping(start, start + size); + if (WARN_ON_ONCE(ret)) + return ret; + + return __update_range_prot(start, size, set_mask, clear_mask); +} + static int __change_memory_common(unsigned long start, unsigned long size, pgprot_t set_mask, pgprot_t clear_mask) { @@ -316,28 +324,52 @@ int set_memory_valid(unsigned long addr, int numpages, int enable) __pgprot(PTE_PRESENT_VALID_KERNEL)); }
-int set_direct_map_invalid_noflush(struct page *page, unsigned int numpages) +int __set_direct_map_invalid_noflush(struct page *page, unsigned int numpages) { pgprot_t clear_mask = __pgprot(PTE_PRESENT_VALID_KERNEL); pgprot_t set_mask = __pgprot(PTE_PRESENT_INVALID);
+ return __update_range_prot((unsigned long)page_address(page), + PAGE_SIZE * numpages, set_mask, clear_mask); +} + +int set_direct_map_invalid_noflush(struct page *page, unsigned int numpages) +{ + unsigned long start = (unsigned long)page_address(page); + int ret; + if (!can_set_direct_map()) return 0;
- return update_range_prot((unsigned long)page_address(page), - PAGE_SIZE * numpages, set_mask, clear_mask); + ret = split_kernel_leaf_mapping(start, start + PAGE_SIZE * numpages); + if (WARN_ON_ONCE(ret)) + return ret; + + return __set_direct_map_invalid_noflush(page, numpages); }
-int set_direct_map_default_noflush(struct page *page, unsigned int numpages) +int __set_direct_map_default_noflush(struct page *page, unsigned int numpages) { pgprot_t set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE); pgprot_t clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY);
+ return __update_range_prot((unsigned long)page_address(page), + PAGE_SIZE * numpages, set_mask, clear_mask); +} + +int set_direct_map_default_noflush(struct page *page, unsigned int numpages) +{ + unsigned long start = (unsigned long)page_address(page); + int ret; + if (!can_set_direct_map()) return 0;
- return update_range_prot((unsigned long)page_address(page), - PAGE_SIZE * numpages, set_mask, clear_mask); + ret = split_kernel_leaf_mapping(start, start + PAGE_SIZE * numpages); + if (WARN_ON_ONCE(ret)) + return ret; + + return __set_direct_map_default_noflush(page, numpages); }
int __set_memory_enc_dec(unsigned long addr, int numpages, bool encrypt)
When memory is lent to secure world, fatal CPU speculative reads can still occur while it retains a cacheable mapping. Use the lendable-dma-pool API to unmap pages before lending and restore them upon reclaim.
Signed-off-by: Vincent Donnefort vdonnefort@google.com --- drivers/tee/optee/protmem.c | 48 ++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-)
diff --git a/drivers/tee/optee/protmem.c b/drivers/tee/optee/protmem.c index be3abf6e8aa6..10e8f1540e5e 100644 --- a/drivers/tee/optee/protmem.c +++ b/drivers/tee/optee/protmem.c @@ -4,6 +4,7 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/dma-mapping.h> #include <linux/errno.h> #include <linux/genalloc.h> #include <linux/slab.h> @@ -21,6 +22,7 @@ struct optee_protmem_dyn_pool { u_int mem_attr_count; refcount_t refcount; u32 use_case; + bool unmapped; struct tee_shm *protmem; /* Protects when initializing and tearing down this struct */ struct mutex mutex; @@ -32,6 +34,35 @@ to_protmem_dyn_pool(struct tee_protmem_pool *pool) return container_of(pool, struct optee_protmem_dyn_pool, pool); }
+static int zap_direct_map_dyn_protmem(struct optee_protmem_dyn_pool *rp) +{ + int ret; + + ret = dma_zap_direct_map(&rp->optee->teedev->dev, + phys_to_page(rp->protmem->paddr), + rp->protmem->size); + if (ret && ret != -ENODEV) + return ret; + + rp->unmapped = !ret; + + return 0; +} + +static void restore_direct_map_dyn_protmem(struct optee_protmem_dyn_pool *rp) +{ + if (WARN_ON(!rp->protmem)) + return; + + if (!rp->unmapped) + return; + + dma_restore_direct_map(&rp->optee->teedev->dev, + phys_to_page(rp->protmem->paddr), + rp->protmem->size); + rp->unmapped = false; +} + static int init_dyn_protmem(struct optee_protmem_dyn_pool *rp) { int rc; @@ -42,19 +73,15 @@ static int init_dyn_protmem(struct optee_protmem_dyn_pool *rp) goto err_null_protmem; }
- /* - * TODO unmap the memory range since the physical memory will - * become inaccesible after the lend_protmem() call. - * - * If the platform supports a hypervisor at EL2, it will unmap the - * intermediate physical memory for us and stop cache pre-fetch of - * the memory. - */ + rc = zap_direct_map_dyn_protmem(rp); + if (rc) + goto err_put_shm; + rc = rp->optee->ops->lend_protmem(rp->optee, rp->protmem, rp->mem_attrs, rp->mem_attr_count, rp->use_case); if (rc) - goto err_put_shm; + goto err_restore_direct_map; rp->protmem->flags |= TEE_SHM_DYNAMIC;
rp->gen_pool = gen_pool_create(PAGE_SHIFT, -1); @@ -76,6 +103,8 @@ static int init_dyn_protmem(struct optee_protmem_dyn_pool *rp) rp->gen_pool = NULL; err_reclaim: rp->optee->ops->reclaim_protmem(rp->optee, rp->protmem); +err_restore_direct_map: + restore_direct_map_dyn_protmem(rp); err_put_shm: tee_shm_put(rp->protmem); err_null_protmem: @@ -112,6 +141,7 @@ static void release_dyn_protmem(struct optee_protmem_dyn_pool *rp) rp->gen_pool = NULL;
rp->optee->ops->reclaim_protmem(rp->optee, rp->protmem); + restore_direct_map_dyn_protmem(rp); rp->protmem->flags &= ~TEE_SHM_DYNAMIC;
WARN(refcount_read(&rp->protmem->refcount) != 1, "Unexpected refcount");
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [High] optee: restore_direct_map_dyn_protmem() called when reclaim_protmem() fails --
commit 1a801ca7487b0859c4bfdf62e4f1116b16a1b7fe Author: Vincent Donnefort vdonnefort@google.com
optee: Add support for lendable-dma-pool
This commit uses dma_zap_direct_map() and dma_restore_direct_map() to unmap dynamically allocated protected memory from the kernel linear map before lending it to the secure world and restore it upon reclaim.
diff --git a/drivers/tee/optee/protmem.c b/drivers/tee/optee/protmem.c index be3abf6e8aa6d..10e8f1540e5e8 100644 --- a/drivers/tee/optee/protmem.c +++ b/drivers/tee/optee/protmem.c
[ ... ]
@@ -76,6 +103,8 @@ static int init_dyn_protmem(struct optee_protmem_dyn_pool *rp) rp->gen_pool = NULL; err_reclaim: rp->optee->ops->reclaim_protmem(rp->optee, rp->protmem); +err_restore_direct_map:
- restore_direct_map_dyn_protmem(rp);
err_put_shm: tee_shm_put(rp->protmem); err_null_protmem: @@ -112,6 +141,7 @@ static void release_dyn_protmem(struct optee_protmem_dyn_pool *rp) rp->gen_pool = NULL; rp->optee->ops->reclaim_protmem(rp->optee, rp->protmem);
- restore_direct_map_dyn_protmem(rp); rp->protmem->flags &= ~TEE_SHM_DYNAMIC;
WARN(refcount_read(&rp->protmem->refcount) != 1, "Unexpected refcount");
[Severity: High] Can restore_direct_map_dyn_protmem() run while rp->protmem is still lent to the secure world if reclaim_protmem() fails?
reclaim_protmem() can fail, for example if optee_get_msg_arg() returns -ENOMEM in optee_smc_reclaim_protmem() or if the secure world has not relinquished the memory handle in optee_ffa_reclaim_protmem(). Both init_dyn_protmem() and release_dyn_protmem() ignore that return value and unconditionally call restore_direct_map_dyn_protmem(rp).
On arm64, arch_dma_restore_direct_map() remaps the pages as cacheable in the linear map and runs dcache_inval_poc() across the range, which re-exposes the still-lent pages to CPU speculative reads and triggers a synchronous external abort or SError on the cache maintenance before tee_shm_put() returns the pages to the allocator.
op-tee@lists.trustedfirmware.org