PCuABI needs the address space reservation interfaces to manage the owning capability of the allocated addresses. This interface prevents two unrelated owning capabilities created by the kernel to overlap.
The reservation interface stores the ranges of different virtual addresses as reservation entries which is same as the bound of the capability provided by the kernel to userspace. It also stores the owning capability permissions to manage the future syscall requests for updating permissions.
Few basic rules are followed by the reservation interfaces:
- Reservations can only be created or destroyed and they are 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 region of the reservation cannot be re-used again. - Reservations start address is aligned to CHERI representable base. - Reservations length value is aligned to CHERI representable length.
More rules about the address space reservation interface can be found in the PCuABI specification.
This commit introduces API's reserv_vma_set_reserv(), reserv_range_set_reserv(), reserv_vmi_range_mapped(), reserv_vmi_valid_capability(), reserv_vma_valid_capability(), reserv_vma_valid_address(), reserv_vma_same_reserv() and reserv_vma_is_supported(). Here except reserv_range_set_reserv(), all other involves single vma. All the above interfaces will be used in different memory management syscalls in subsequent patches.
Signed-off-by: Amit Daniel Kachhap amitdaniel.kachhap@arm.com --- include/linux/cap_addr_mgmt.h | 196 ++++++++++++++++++++++++++++++++++ include/linux/mm_types.h | 5 + mm/Makefile | 2 +- mm/cap_addr_mgmt.c | 176 ++++++++++++++++++++++++++++++ 4 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 include/linux/cap_addr_mgmt.h create mode 100644 mm/cap_addr_mgmt.c
diff --git a/include/linux/cap_addr_mgmt.h b/include/linux/cap_addr_mgmt.h new file mode 100644 index 000000000000..2f296f02c3ff --- /dev/null +++ b/include/linux/cap_addr_mgmt.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_CAP_ADDR_MGMT_H +#define _LINUX_CAP_ADDR_MGMT_H + +#include <linux/cheri.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mm_types.h> +#include <linux/sched/coredump.h> +#include <linux/types.h> + +#ifdef CONFIG_CHERI_PURECAP_UABI +#define CHERI_REPRESENTABLE_ALIGNMENT(len) \ + (~cheri_representable_alignment_mask(len) + 1) + +#define CHERI_REPRESENTABLE_BASE(base, len) \ + ((base) & cheri_representable_alignment_mask(len)) + +/** + * reserv_vma_set_reserv() - Sets the reservation details in the VMA for the + * virtual address range from start to (start + len) with perm permission as + * the entry. The start address are stored as CHERI representable base and the + * length as CHERI representable length. They are expected to not interfere + * with the successive vma. This function should be called with mmap_lock + * held. + * @vma: VMA pointer to insert the reservation entry. + * @start: Reservation start value. + * @len: Reservation length. + * @perm: Capability permission for the reserved range. + * + * Return: 0 if reservation entry added successfully or -ERESERVATION + * otherwise. + */ +int reserv_vma_set_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len, cheri_perms_t perm); + +/** + * reserv_range_set_reserv() - Sets the reservation details across the VMA's + * for the virtual address range from start to (start + len) with the perm + * permission as the entry. The start address is expected to be CHERI + * representable base and the length to be CHERI representable length. + * This function internally uses mmap_lock to synchronize the vma updates + * if mmap_lock not already held. + * @start: Reservation start value. + * @len: Reservation length. + * @perm: Capability permission for the reserved range. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: valid capability with bounded range and requested permission or + * negative errorcode otherwise. + */ +user_uintptr_t reserv_range_set_reserv(ptraddr_t start, size_t len, + cheri_perms_t perm, bool locked); + +/** + * reserv_vmi_range_mapped_unlocked() - Searches the reservation interface for + * the virtual address range from start to (start + len). This is useful to + * find if the requested range maps completely and there is no fragmentation. + * This function internally uses mmap_lock to synchronize the vma updates + * if mmap_lock not already held. + * @vmi: The vma iterator pointing at the vma. + * @start: Virtual address start value. + * @len: Virtual address length. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: 0 if the vma mapping matches fully with the given range or negative + * errorcode otherwise. + */ +int reserv_vmi_range_mapped(struct vma_iterator *vmi, ptraddr_t start, + size_t len, bool locked); + +/** + * reserv_vmi_valid_capability() - Search and matches the reservation interface + * for the capability bound values falling within the reserved virtual address + * range. This function internally uses mmap_lock to synchronize the vma + * updates if mmap_lock not already held. + * @vmi: The vma iterator pointing at the vma. + * @cap: Reservation capability value. + * @locked: Flag to indicate if mmap_lock is already held. + * + * Return: True if the input capability bound values within the reserved virtual + * address range or false otherwise. + */ +bool reserv_vmi_valid_capability(struct vma_iterator *vmi, user_uintptr_t cap, + bool locked); + +/** + * reserv_vma_valid_capability() - Search and matches the input vma for the + * capability bound values falling within the reserved virtual address range. + * This function should be called with mmap_lock held. + * @vma: The vma pointer. + * @cap: Reservation capability value. + * + * Return: True if the input capability bound values within the reserved virtual + * address range or false otherwise. + */ +bool reserv_vma_valid_capability(struct vm_area_struct *vma, user_uintptr_t cap); + +/** + * reserv_vma_valid_address() - Search and matches the input vma for the input + * address range falling within the reserved virtual address range. This + * function should be called with mmap_lock held. + * @vma: The vma pointer. + * @start: Virtual address start value. + * @len: Virtual address length. + * + * Return: True if the input address range within the reserved virtual address + * range or false otherwise. + */ +bool reserv_vma_valid_address(struct vm_area_struct *vma, ptraddr_t start, size_t len); + +/** + * reserv_vma_same_reserv() - Compares the reservation properties between + * two vma's. This function should be called with mmap_lock held. + * @vma1: The first vma pointer. + * @vma2: The second vma pointer. + * + * Return: True if two vma's have the same reservation range or false otherwise. + */ +bool reserv_vma_same_reserv(struct vm_area_struct *vma1, struct vm_area_struct *vma2); + +/** + * reserv_vma_is_supported() - Checks if the reservation properties exists + * for the vma. This function should be called with mmap_lock held. + * @vma: The vma pointer. + * + * Return: True if vma has the reservation property set or false otherwise. + */ +bool reserv_vma_is_supported(struct vm_area_struct *vma); + +/** + * reserv_fork() - Checks and copies the MMF_PCUABI_RESERVE bit in the new + * mm during fork. + * @mm: New mm pointer. + * @oldmm: Old mm pointer. + * + * Return: None. + */ +static inline void reserv_fork(struct mm_struct *mm, struct mm_struct *oldmm) +{ + if (test_bit(MMF_PCUABI_RESERVE, &oldmm->flags)) + set_bit(MMF_PCUABI_RESERVE, &mm->flags); +} + +#else /* CONFIG_CHERI_PURECAP_UABI */ + +static inline int reserv_vma_set_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len, cheri_perms_t perm) +{ + return 0; +} + +static inline user_uintptr_t reserv_range_set_reserv(ptraddr_t start, size_t len, + cheri_perms_t perm, bool locked) +{ + return (user_uintptr_t)start; +} + +static inline int reserv_vmi_range_mapped(struct vma_iterator *vmi, ptraddr_t start, + size_t len, bool locked) +{ + return 0; +} + +static inline bool reserv_vmi_valid_capability(struct vma_iterator *vmi, user_uintptr_t cap, + bool locked) +{ + return true; +} + +static inline bool reserv_vma_valid_capability(struct vm_area_struct *vma, user_uintptr_t cap) +{ + return true; +} + +static inline bool reserv_vma_valid_address(struct vm_area_struct *vma, ptraddr_t addr, size_t len) +{ + return true; +} + +static inline bool reserv_vma_same_reserv(struct vm_area_struct *vma1, struct vm_area_struct *vma2) +{ + return true; +} + +static inline bool reserv_vma_is_supported(struct vm_area_struct *vma) +{ + return false; +} + +static inline void reserv_fork(struct mm_struct *mm, struct mm_struct *oldmm) {} + +#endif /* CONFIG_CHERI_PURECAP_UABI */ + +#endif /* _LINUX_CAP_ADDR_MGMT_H */ diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 12e87f83287d..38bad6b201ca 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -571,6 +571,11 @@ struct vm_area_struct { struct vma_numab_state *numab_state; /* NUMA Balancing state */ #endif struct vm_userfaultfd_ctx vm_userfaultfd_ctx; +#ifdef CONFIG_CHERI_PURECAP_UABI + unsigned long reserv_start; + unsigned long reserv_len; + unsigned long reserv_perm; +#endif } __randomize_layout;
#ifdef CONFIG_SCHED_MM_CID diff --git a/mm/Makefile b/mm/Makefile index e29afc890cde..c2ca31fe5798 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -39,7 +39,7 @@ mmu-y := nommu.o mmu-$(CONFIG_MMU) := highmem.o memory.o mincore.o \ mlock.o mmap.o mmu_gather.o mprotect.o mremap.o \ msync.o page_vma_mapped.o pagewalk.o \ - pgtable-generic.o rmap.o vmalloc.o + pgtable-generic.o rmap.o vmalloc.o cap_addr_mgmt.o
ifdef CONFIG_CROSS_MEMORY_ATTACH diff --git a/mm/cap_addr_mgmt.c b/mm/cap_addr_mgmt.c new file mode 100644 index 000000000000..8b5f98d0d01c --- /dev/null +++ b/mm/cap_addr_mgmt.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bug.h> +#include <linux/cap_addr_mgmt.h> +#include <linux/cheri.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#ifdef CONFIG_CHERI_PURECAP_UABI + +int reserv_vma_set_reserv(struct vm_area_struct *vma, ptraddr_t start, + size_t len, cheri_perms_t perm) +{ + if (!reserv_vma_is_supported(vma)) + return 0; + /* Reservation base/length is expected as page aligned */ + VM_BUG_ON(start & ~PAGE_MASK); + VM_BUG_ON(len & ~PAGE_MASK); + + vma->reserv_start = CHERI_REPRESENTABLE_BASE(start, len); + vma->reserv_len = cheri_representable_length(len); + if (perm) + vma->reserv_perm = perm; + + return 0; +} + +user_uintptr_t reserv_range_set_reserv(ptraddr_t start, size_t len, cheri_perms_t perm, bool locked) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + ptraddr_t end = start + len; + user_uintptr_t ret = 0; + VMA_ITERATOR(vmi, mm, start); + + if (!test_bit(MMF_PCUABI_RESERVE, &mm->flags)) + return start; + + if (end < start) + return -EINVAL; + if (end == start) + return 0; + + /* Check if the reservation range is representable and throw error if not */ + if (start & ~cheri_representable_alignment_mask(len) || + len != cheri_representable_length(len) || + start & ~PAGE_MASK || len % PAGE_SIZE) { + printk(KERN_WARNING "Reservation range (0x%lx)-(0x%lx) is not representable\n", + start, start + len - 1); + return -ERESERVATION; + } + if (!locked && mmap_write_lock_killable(mm)) + return -EINTR; + + for_each_vma_range(vmi, vma, end) { + WRITE_ONCE(vma->reserv_start, start); + WRITE_ONCE(vma->reserv_len, len); + WRITE_ONCE(vma->reserv_perm, perm); + } + if (!locked) + mmap_write_unlock(current->mm); + ret = (user_uintptr_t)uaddr_to_user_ptr_safe(start); + + return ret; +} + +int reserv_vmi_range_mapped(struct vma_iterator *vmi, ptraddr_t start, + size_t len, bool locked) +{ + struct vm_area_struct *vma; + struct mm_struct *mm = current->mm; + int ret = -ENOMEM; + + if (!test_bit(MMF_PCUABI_RESERVE, &mm->flags)) + return 0; + + if (!locked && mmap_read_lock_killable(mm)) + return -EINTR; + + start = round_down(start, PAGE_SIZE); + len = round_up(len, PAGE_SIZE); + mas_set_range(&vmi->mas, start, start); + /* Try walking the given range */ + vma = mas_find(&vmi->mas, start + len - 1); + if (!vma) + goto out; + + /* If the range is fully mapped then no gap exists */ + if (mas_empty_area(&vmi->mas, start, start + len - 1, 1)) + goto out; +out: + if (!locked) + mmap_read_unlock(mm); + return ret; +} + +bool reserv_vmi_valid_capability(struct vma_iterator *vmi, user_uintptr_t cap, bool locked) +{ + struct vm_area_struct *vma; + struct mm_struct *mm = current->mm; + ptraddr_t cap_start = cheri_base_get(cap); + ptraddr_t cap_end = cap_start + cheri_length_get(cap); + bool ret = false; + + if (!test_bit(MMF_PCUABI_RESERVE, &mm->flags)) + return true; + + if (!locked && mmap_read_lock_killable(mm)) + return false; + + /* Check if there is match with the existing reservations */ + vma = mas_find(&vmi->mas, cap_end); + if (!vma) { + ret = true; + goto out; + } + + if (vma->reserv_start <= cap_start && + vma->reserv_len >= cheri_length_get(cap)) + ret = true; +out: + if (!locked) + mmap_read_unlock(mm); + + return ret; +} + +bool reserv_vma_valid_capability(struct vm_area_struct *vma, user_uintptr_t cap) +{ + if (!reserv_vma_is_supported(vma)) + return true; + + /* Check if there is match with the existing reservations */ + if (vma->reserv_start <= cheri_base_get(cap) && + vma->reserv_len >= cheri_length_get(cap)) + return true; + + return false; +} + +bool reserv_vma_valid_address(struct vm_area_struct *vma, ptraddr_t start, size_t len) +{ + if (!reserv_vma_is_supported(vma)) + return true; + + /* Check if there is match with the existing reservations */ + if (vma->reserv_start <= start && vma->reserv_len >= len) + return true; + + return false; +} + +bool reserv_vma_same_reserv(struct vm_area_struct *vma1, struct vm_area_struct *vma2) +{ + if (!vma1 || !vma2 || !reserv_vma_is_supported(vma1) || !reserv_vma_is_supported(vma1)) + return true; + + /* Match reservation properties betwen 2 vma's */ + if (reserv_vma_is_supported(vma1) && reserv_vma_is_supported(vma1) + && vma1->reserv_start == vma2->reserv_start + && vma1->reserv_len == vma2->reserv_len) + return true; + + return false; +} + +bool reserv_vma_is_supported(struct vm_area_struct *vma) +{ + if (vma && vma->vm_mm && test_bit(MMF_PCUABI_RESERVE, + &vma->vm_mm->flags)) + return true; + + return false; +} + +#endif /* CONFIG_CHERI_PURECAP_UABI */