From: Amit Daniel Kachhap amitdaniel.kachhap@arm.com
This patch introduces helpers to manage address space reservations and owning capabilities, as introduced in the PCuABI specification. This interface prevents two unrelated owning capabilities created by the kernel from overlapping.
The reservation interface stores virtual address ranges as reservation entries, which is the same as the bounds of the owning capability provided by the kernel to userspace. It also stores the owning capability permissions to manage the future syscall requests for updating permissions.
The reservation interface follows a few basic rules:
- Reservations can only be created or destroyed but never expanded or shrunk. Reservations are created when new memory mapping is made outside of an existing reservation. - A single reservation can have many mappings. However, unused regions of the reservation cannot be reused again. - The Reservation start address is aligned to CHERI representable base. - The Reservation length value is aligned to CHERI representable length.
More rules about the address space reservation interface can be found in the PCuABI specification.
Signed-off-by: Amit Daniel Kachhap amitdaniel.kachhap@arm.com Co-developed-by: Kevin Brodsky kevin.brodsky@arm.com Signed-off-by: Kevin Brodsky kevin.brodsky@arm.com --- include/linux/mm_reserv.h | 302 ++++++++++++++++++++++++++++++++++++++ mm/Makefile | 1 + mm/reserv.c | 181 +++++++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 include/linux/mm_reserv.h create mode 100644 mm/reserv.c
diff --git a/include/linux/mm_reserv.h b/include/linux/mm_reserv.h new file mode 100644 index 000000000000..2debbcbf7495 --- /dev/null +++ b/include/linux/mm_reserv.h @@ -0,0 +1,302 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_MM_RESERV_H +#define _LINUX_MM_RESERV_H + +#include <linux/cheri.h> +#include <linux/mm_types.h> +#include <linux/sched/coredump.h> +#include <linux/types.h> + +#ifdef CONFIG_CHERI_PURECAP_UABI +#define reserv_representable_alignment(len) \ + (reserv_is_supported(current->mm) \ + ? (PAGE_MASK & ~cheri_representable_alignment_mask(len)) : 0) + +#define reserv_representable_base(base, len) \ + (reserv_is_supported(current->mm) \ + ? ((base) & cheri_representable_alignment_mask(len)) : (base)) + +#define reserv_representable_length(len) \ + (reserv_is_supported(current->mm) \ + ? cheri_representable_length(len) : (len)) + +#define reserv_vma_reserv_start(vma) \ + (reserv_is_supported((vma)->vm_mm) \ + ? (vma)->reserv_data.start : (vma)->vm_start) + +#define reserv_vma_reserv_len(vma) \ + (reserv_is_supported((vma)->vm_mm) \ + ? (vma)->reserv_data.len : ((vma)->vm_end - (vma)->vm_start)) + +/** + * reserv_vma_set_reserv() - Set the reservation information in the VMA. + * @vma: Target VMA. + * @start: Reservation start address. + * @len: Reservation length. + * @prot: prot flags to calculate the reservation permissions. + * + * Return: 0 if reservation information set successfully or negative errorcode + * otherwise. + * + * The start address is stored as CHERI representable base and the length as + * CHERI representable length. They are expected to not overlap with any other + * VMA. This function should be called with mmap_lock held. + */ +int reserv_vma_set_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len, int prot); + +/** + * reserv_vma_set_reserv_start_len() - Set the reservation information in the VMA. + * @vma: Target VMA. + * @start: Reservation start address. + * @len: Reservation length. + * + * Return: 0 if reservation information set successfully or negative errorcode + * otherwise. + * + * The start address is stored as CHERI representable base and the length as + * CHERI representable length. They are expected to not overlap with any other + * VMA. The reservation permissions are left unchanged. This function should + * be called with mmap_lock held. + */ +int reserv_vma_set_reserv_start_len(struct vm_area_struct *vma, ptraddr_t start, + size_t len); + +/** + * reserv_vma_set_reserv_data() - Set the reservation information in the VMA. + * @vma: Target VMA. + * @reserv_data: New reservation information + * + * The VMA's reservation information is set to the contents of @reserv_data. + * This function should be called with mmap_lock held. + */ +void reserv_vma_set_reserv_data(struct vm_area_struct *vma, + const struct reserv_struct *reserv_data); + +/** + * reserv_find_reserv_info_range() - Find a reservation spanning at least the + * input address range. + * @start: Region start address. + * @len: Region length. + * @locked: Flag to indicate if mmap_lock is already held. + * @reserv_info: Pointer to a reserv_struct to set if a matching reservation is + * found. + * + * Return: True if a matching reservation is found or false otherwise. + * + * This function internally uses mmap_lock to access VMAs if mmap_lock is not + * already held. + + */ +bool reserv_find_reserv_info_range(ptraddr_t start, size_t len, bool locked, + struct reserv_struct *reserv_info); + +/** + * reserv_vma_range_within_reserv() - Check that the input address range falls + * within @vma's reservation. + * @vma: Target VMA. + * @start: Region start address. + * @len: Region length. + * + * Return: True if the input address range falls within the reserved virtual + * address range or false otherwise. + * + * This function should be called with mmap_lock held. + */ +bool reserv_vma_range_within_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len); + +/** + * reserv_cap_within_reserv() - Check that the capability bounds of @cap + * are wholly contained within an existing reservation. + * @cap: Capability to check. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: True if the input capability bounds fall within a reservation or + * false otherwise. + * + * This function internally uses mmap_lock to access VMAs if mmap_lock is not + * already held. + */ +bool reserv_cap_within_reserv(user_uintptr_t cap, bool locked); + +/** + * reserv_aligned_range_within_reserv() - Check that the input address range falls + * within any reservation. + * @start: Region start address. + * @len: Region length. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: True if the input address range (aligned for representability) falls + * within a reservation or false otherwise. + * + * @start and @len are appropriately aligned down/up so that the range that is + * checked corresponds to that of a new reservation. This function should be + * called with mmap_lock held. + */ +bool reserv_aligned_range_within_reserv(ptraddr_t start, size_t len, + bool locked); + +/** + * reserv_range_mapped() - Check that the input address range is fully mapped. + * @start: Region start address. + * @len: Region length. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: 0 if the range is fully mapped or negative errorcode otherwise. + * + * This is useful to find if the requested range is fully mapped without + * fragmentation. This function internally uses mmap_lock to access VMAs if + * mmap_lock is not already held. + */ +int reserv_range_mapped(ptraddr_t start, size_t len, bool locked); + +/** + * reserv_make_user_ptr_owning() - Build an owning user pointer for a given + * reservation. + * @vma_addr: VMA address. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: the constructed user pointer. + * + * @vma_addr must be the address of an existing VMA, whose reservation + * information will be used to set the user pointer's bounds and permissions. + * Its address will be set to @vma_addr. This function internally uses + * mmap_lock to access VMAs if mmap_lock is not already held. + */ +user_uintptr_t reserv_make_user_ptr_owning(ptraddr_t vma_addr, bool locked); + +/** + * reserv_vma_make_user_ptr_owning() - Build an owning user pointer for a given + * reservation. + * @vma: Target VMA. + * + * Return: the constructed user pointer. + * + * @vma's reservation information will be used to set the user + * pointer's bounds and permissions. Its address will be set to @vma's start + * address. This function should be called with mmap_lock held. + */ +user_uintptr_t reserv_vma_make_user_ptr_owning(struct vm_area_struct *vma); + +/** + * reserv_is_supported() - Check if reservations are enabled for the given mm. + * + * @mm: The mm pointer. + * + * Return: True if mm has reservations enabled or false otherwise. + */ +static inline bool reserv_is_supported(struct mm_struct *mm) +{ + return test_bit(MMF_PCUABI_RESERV, &mm->flags); +} + +/** + * reserv_mm_set_flag() - Set the MMF_PCUABI_RESERV flag according to @compat. + * + * @mm: mm pointer. + * @compat: Flag indicating if the current task is compat. + */ +static inline void reserv_mm_set_flag(struct mm_struct *mm, bool compat) +{ + if (compat) + clear_bit(MMF_PCUABI_RESERV, &mm->flags); + else + set_bit(MMF_PCUABI_RESERV, &mm->flags); +} + +/** + * reserv_fork() - Copy the MMF_PCUABI_RESERV flag from @oldmm to @mm. + * + * @mm: New mm pointer. + * @oldmm: Old mm pointer. + */ +static inline void reserv_fork(struct mm_struct *mm, struct mm_struct *oldmm) +{ + if (test_bit(MMF_PCUABI_RESERV, &oldmm->flags)) + set_bit(MMF_PCUABI_RESERV, &mm->flags); +} + +#else /* CONFIG_CHERI_PURECAP_UABI */ + +#define reserv_representable_alignment(len) 0 + +#define reserv_representable_base(base, len) base + +#define reserv_representable_length(len) len + +#define reserv_vma_reserv_start(vma) vma->vm_start + +#define reserv_vma_reserv_len(vma) (vma->vm_end - vma->vm_start) + +static inline int reserv_vma_set_reserv(struct vm_area_struct *vma, + ptraddr_t start, size_t len, int prot) +{ + return 0; +} + +static inline int reserv_vma_set_reserv_start_len(struct vm_area_struct *vma, + ptraddr_t start, size_t len) +{ + return 0; +} + +static inline void reserv_vma_set_reserv_data(struct vm_area_struct *vma, + const struct reserv_struct *reserv_data) +{} + +static inline bool reserv_find_reserv_info_range(ptraddr_t start, + size_t len, bool locked, + struct reserv_struct *reserv_info) +{ + return true; +} + +static inline bool reserv_vma_range_within_reserv(struct vm_area_struct *vma, + ptraddr_t start, + size_t len) +{ + return true; +} + +static inline bool reserv_cap_within_reserv(user_uintptr_t cap, bool locked) +{ + return true; +} + +static inline bool reserv_aligned_range_within_reserv(ptraddr_t start, + size_t len, + bool locked) +{ + return true; +} + +static inline int reserv_range_mapped(ptraddr_t start, size_t len, bool locked) +{ + return 0; +} + +static inline user_uintptr_t reserv_make_user_ptr_owning(ptraddr_t vma_addr, + bool locked) +{ + return vma_addr; +} + +static inline user_uintptr_t reserv_vma_make_user_ptr_owning(struct vm_area_struct *vma) +{ + return vma->vm_start; +} + +static inline bool reserv_is_supported(struct mm_struct *mm) +{ + return false; +} + +static inline void reserv_mm_set_flag(struct mm_struct *mm, bool compat) {} + +static inline void reserv_fork(struct mm_struct *mm, struct mm_struct *oldmm) {} + +#endif /* CONFIG_CHERI_PURECAP_UABI */ + +#endif /* _LINUX_MM_RESERV_H */ diff --git a/mm/Makefile b/mm/Makefile index 33873c8aedb3..94a7ab7057f0 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -41,6 +41,7 @@ mmu-$(CONFIG_MMU) := highmem.o memory.o mincore.o \ msync.o page_vma_mapped.o pagewalk.o \ pgtable-generic.o rmap.o vmalloc.o
+mmu-$(CONFIG_CHERI_PURECAP_UABI) += reserv.o
ifdef CONFIG_CROSS_MEMORY_ATTACH mmu-$(CONFIG_MMU) += process_vm_access.o diff --git a/mm/reserv.c b/mm/reserv.c new file mode 100644 index 000000000000..de5a3095a863 --- /dev/null +++ b/mm/reserv.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bug.h> +#include <linux/mm_reserv.h> +#include <linux/mm.h> +#include <linux/slab.h> + +int reserv_vma_set_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len, int prot) +{ + if (!reserv_is_supported(vma->vm_mm)) + return 0; + if (start + len < start) + return -EINVAL; + /* Reservation base/length is expected as page aligned */ + VM_BUG_ON(start & ~PAGE_MASK || len % PAGE_SIZE); + + vma->reserv_data.start = start & cheri_representable_alignment_mask(len); + vma->reserv_data.len = cheri_representable_length(len); + vma->reserv_data.perms = user_ptr_owning_perms_from_prot(prot, + vma->vm_flags); + + return 0; +} + +int reserv_vma_set_reserv_start_len(struct vm_area_struct *vma, ptraddr_t start, + size_t len) +{ + if (!reserv_is_supported(vma->vm_mm)) + return 0; + if (start + len < start) + return -EINVAL; + /* Reservation base/length is expected as page aligned */ + VM_BUG_ON(start & ~PAGE_MASK || len % PAGE_SIZE); + + vma->reserv_data.start = start & cheri_representable_alignment_mask(len); + vma->reserv_data.len = cheri_representable_length(len); + + return 0; +} + +void reserv_vma_set_reserv_data(struct vm_area_struct *vma, + const struct reserv_struct *reserv_data) +{ + if (!reserv_is_supported(vma->vm_mm)) + return; + + vma->reserv_data = *reserv_data; +} + +bool reserv_find_reserv_info_range(ptraddr_t start, size_t len, + bool locked, struct reserv_struct *reserv_info) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *next, *prev; + struct reserv_struct *info = NULL; + + if (!reserv_is_supported(mm)) + return true; + if (!locked && mmap_read_lock_killable(mm)) + return false; + + next = find_vma_prev(mm, start, &prev); + + if (next && reserv_vma_range_within_reserv(next, start, len)) + info = &next->reserv_data; + else if (prev && reserv_vma_range_within_reserv(prev, start, len)) + info = &prev->reserv_data; + + if (info && reserv_info) + *reserv_info = *info; + + if (!locked) + mmap_read_unlock(mm); + + return !!info; +} + +bool reserv_vma_range_within_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len) +{ + if (!reserv_is_supported(vma->vm_mm)) + return true; + + /* Check if there is match with the existing reservations */ + return vma->reserv_data.start <= start && + vma->reserv_data.start + vma->reserv_data.len >= start + len; +} + +bool reserv_cap_within_reserv(user_uintptr_t cap, bool locked) +{ + return reserv_find_reserv_info_range(cheri_base_get(cap), + cheri_length_get(cap), + locked, NULL); +} + +bool reserv_aligned_range_within_reserv(ptraddr_t start, size_t len, + bool locked) +{ + ptraddr_t aligned_start = start & cheri_representable_alignment_mask(len); + size_t aligned_len = cheri_representable_length(len); + + if (start + len < start) + return false; + + return reserv_find_reserv_info_range(aligned_start, aligned_len, + locked, NULL); +} + +int reserv_range_mapped(ptraddr_t start, size_t len, bool locked) +{ + struct vm_area_struct *vma, *last_vma = NULL; + struct mm_struct *mm = current->mm; + ptraddr_t end = start + len - 1; + int ret = -ENOMEM; + VMA_ITERATOR(vmi, mm, 0); + + if (!reserv_is_supported(mm)) + return 0; + if (!locked && mmap_read_lock_killable(mm)) + return -EINTR; + + start = untagged_addr(start); + start = round_down(start, PAGE_SIZE); + len = round_up(len, PAGE_SIZE); + vma_iter_set(&vmi, start); + /* Try walking the given range */ + do { + vma = mas_find(&vmi.mas, end); + if (vma) { + /* The new and old vma should be continuous */ + if (last_vma && last_vma->vm_end != vma->vm_start) + goto out; + /* End range is within the vma so return success */ + if (end < vma->vm_end) { + ret = 0; + goto out; + } + last_vma = vma; + } + } while (vma); +out: + if (!locked) + mmap_read_unlock(mm); + return ret; +} + +user_uintptr_t reserv_make_user_ptr_owning(ptraddr_t vma_addr, bool locked) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + struct reserv_struct reserv; + + if (!reserv_is_supported(mm)) + return vma_addr; + if (!locked && mmap_read_lock_killable(mm)) + return vma_addr; + + vma = find_vma(mm, vma_addr); + + if (WARN_ON(!vma || vma->vm_start != vma_addr)) { + if (!locked) + mmap_read_unlock(mm); + return vma_addr; + } + + reserv = vma->reserv_data; + + if (!locked) + mmap_read_unlock(mm); + + return make_user_ptr_owning(&reserv, vma_addr); +} + +user_uintptr_t reserv_vma_make_user_ptr_owning(struct vm_area_struct *vma) +{ + if (!reserv_is_supported(vma->vm_mm)) + return vma->vm_start; + + return make_user_ptr_owning(&vma->reserv_data, vma->vm_start); +}