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_insert_entry(), reserv_range_insert_entry(), reserv_vmi_range_fully_mapped(), reserv_vma_range_fully_mapped(), reserv_vmi_match_capability(), reserv_vma_match_capability() and reserv_vma_match_properties(). Here except reserv_range_insert_entry(), 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 amit.kachhap@arm.com --- include/linux/cap_addr_mgmt.h | 116 ++++++++++++++++++ include/linux/mm_types.h | 5 + mm/Makefile | 2 +- mm/cap_addr_mgmt.c | 222 ++++++++++++++++++++++++++++++++++ 4 files changed, 344 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..00eda91c2fc0 --- /dev/null +++ b/include/linux/cap_addr_mgmt.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_CAP_ADDR_MGMT_H +#define _LINUX_CAP_ADDR_MGMT_H + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/mm_types.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)) +#endif + +/** + * reserv_vma_insert_entry() - Adds 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 is expected to be CHERI representable base + * and the length may expand to CHERI representable length without interfering + * 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: Memory mapping permission for the reserved range. + * + * Return: 0 if reservation entry added successfully or -ERESERVATION + * otherwise. + */ +int reserv_vma_insert_entry(struct vm_area_struct *vma, unsigned long start, + unsigned long len, unsigned long perm); + +/** + * reserv_range_insert_entry() - Adds 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. + * @start: Reservation start value. + * @len: Reservation length. + * @perm: Memory mapping permission for the reserved range. + * + * Return: valid capability with bounded range and requested permission or + * negative errorcode otherwise. + */ +user_uintptr_t reserv_range_insert_entry(unsigned long start, unsigned long len, + unsigned long perm); + +/** + * reserv_vmi_range_fully_mapped() - Searches the reservation interface for the + * virtual address range from start to (start + len). This is useful to find + * if the requested range maps exactly with the reserved range and there is no + * fragmentation. This function should be called with mmap_lock held. + * @vmi: The vma iterator pointing at the vma. + * @start: Virtual address start value. + * @len: Virtual address length. + * + * Return: True if the vma mapping matches fully with the given range or false + * otherwise. + */ +bool reserv_vmi_range_fully_mapped(struct vma_iterator *vmi, unsigned long start, + unsigned long len); + +/** + * reserv_vma_range_fully_mapped() - Searches the reservation interface for the + * virtual address range from start to (start + len). This is useful to find + * if the requested range maps exactly with the reserved range and there is no + * fragmentation. 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 vma mapping matches fully with the given range or false + * otherwise. + */ +bool reserv_vma_range_fully_mapped(struct vm_area_struct *vma, unsigned long start, + unsigned long len); + +/** + * reserv_vmi_match_capability() - Search and matches the reservation interface + * for the virtual address range derived from the capability bound values. + * This function should be called with mmap_lock held. + * @vmi: The vma iterator pointing at the vma. + * @start: Reservation capability value. + * + * Return: True if reservation entry found with the exact capability bound or + * false otherwise. + */ +bool reserv_vmi_match_capability(struct vma_iterator *vmi, user_uintptr_t start); + +/** + * reserv_vma_match_capability() - Search and matches the reservation interface + * for the virtual address range derived from the capability bound values. + * This function should be called with mmap_lock held. + * @vma: The vma pointer. + * @start: Reservation capability value. + * + * Return: True if reservation entry found with the exact capability bound or + * false otherwise. + */ +bool reserv_vma_match_capability(struct vm_area_struct *vma, user_uintptr_t start); + +/** + * reserv_vma_match_properties() - 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_match_properties(struct vm_area_struct *vma1, struct vm_area_struct *vma2); +#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..fe318c92dc7a --- /dev/null +++ b/mm/cap_addr_mgmt.c @@ -0,0 +1,222 @@ +// 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_insert_entry(struct vm_area_struct *vma, unsigned long start, + unsigned long len, unsigned long perm) +{ + /* TODO [PCuABI] - capability permission conversion from memory permission */ + cheri_perms_t cheri_perms = CHERI_PERMS_READ | CHERI_PERMS_WRITE | + CHERI_PERMS_EXEC | CHERI_PERMS_ROOTCAP; + + if (is_compat_task() || !(vma->vm_flags & VM_PCUABI_RESERVE)) + return 0; + + /* Check if the reservation base is representable */ + if (start & ~cheri_representable_alignment_mask(len) || start & ~PAGE_MASK) { + printk(KERN_WARNING "Reservation base address(0x%lx) is not representable\n", + start); + return -ERESERVATION; + } + + /* Check if the reservation length is page aligned */ + if (len % PAGE_SIZE) { + printk(KERN_WARNING "Reservation base address(0x%lx) is not representable\n", + start); + return -ERESERVATION; + } + vma->reserv_start = start; + vma->reserv_len = cheri_representable_length(len); + if (perm) + vma->reserv_perm = cheri_perms; + + return 0; +} + +user_uintptr_t reserv_range_insert_entry(unsigned long start, unsigned long len, + unsigned long perm __maybe_unused) +{ + /* TODO [PCuABI] - capability permission conversion from memory permission */ + cheri_perms_t cheri_perm = CHERI_PERMS_READ | CHERI_PERMS_WRITE | + CHERI_PERMS_EXEC | CHERI_PERMS_ROOTCAP; + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long end = start + len; + user_uintptr_t ret = 0; + VMA_ITERATOR(vmi, mm, start); + + if (is_compat_task()) + 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 (mmap_write_lock_killable(mm)) + return -EINTR; + + for_each_vma_range(vmi, vma, end) { + vm_flags_set(vma, VM_PCUABI_RESERVE); + WRITE_ONCE(vma->reserv_start, start); + WRITE_ONCE(vma->reserv_len, len); + WRITE_ONCE(vma->reserv_perm, cheri_perm); + } + mmap_write_unlock(current->mm); + ret = (user_uintptr_t)uaddr_to_user_ptr_safe(start); + + return ret; +} + +bool reserv_vmi_range_fully_mapped(struct vma_iterator *vmi, unsigned long start, + unsigned long len) +{ + unsigned long align_start, align_len; + struct vm_area_struct *vma; + + if (is_compat_task()) + return true; + + mas_set_range(&vmi->mas, start, start + len); + + /* Try finding the given range */ + vma = mas_find(&vmi->mas, start + len); + if (!vma || !(vma->vm_flags & VM_PCUABI_RESERVE)) + return true; + + align_start = round_down(vma->vm_start, max(PAGE_SIZE, CHERI_REPRESENTABLE_ALIGNMENT(len))); + align_len = cheri_representable_length(round_up((vma->vm_end - vma->vm_start), PAGE_SIZE)); + /* Check if the range fully mapped */ + if (start != vma->vm_start || (start + len) != vma->vm_end || + align_start != vma->reserv_start || + align_len != vma->reserv_len) + return false; + + return true; +} + +bool reserv_vma_range_fully_mapped(struct vm_area_struct *vma, unsigned long start, + unsigned long len) +{ + unsigned long align_start, align_len; + + if (is_compat_task() || !vma || !(vma->vm_flags & VM_PCUABI_RESERVE)) + return true; + + align_start = round_down(vma->vm_start, max(PAGE_SIZE, CHERI_REPRESENTABLE_ALIGNMENT(len))); + align_len = cheri_representable_length(round_up((vma->vm_end - vma->vm_start), PAGE_SIZE)); + + /* Check if the range fully mapped */ + if (start != vma->vm_start || (start + len) != vma->vm_end || + align_start != vma->reserv_start || + align_len != vma->reserv_len) + return false; + + return true; +} + +bool reserv_vmi_match_capability(struct vma_iterator *vmi, user_uintptr_t start) +{ + struct vm_area_struct *vma; + unsigned long reserv_start = cheri_base_get(start); + unsigned long reserv_end = reserv_start + cheri_length_get(start); + + if (is_compat_task()) + return true; + + /* Check if there is match with the existing reservations */ + vma = mas_find(&vmi->mas, reserv_end); + if (!vma || !(vma->vm_flags & VM_PCUABI_RESERVE)) + return true; + + if (vma->reserv_start == reserv_start && + vma->reserv_len == cheri_length_get(start)) + return true; + + return false; +} + +bool reserv_vma_match_capability(struct vm_area_struct *vma, user_uintptr_t start) +{ + if (is_compat_task() || !vma || !(vma->vm_flags & VM_PCUABI_RESERVE)) + return true; + + /* Check if there is match with the existing reservations */ + if (vma->reserv_start == cheri_base_get(start) && + vma->reserv_len == cheri_length_get(start)) + return true; + + return false; +} + +bool reserv_vma_match_properties(struct vm_area_struct *vma1, struct vm_area_struct *vma2) +{ + if (is_compat_task()) + return true; + + /* Match reservation properties betwen 2 vma's */ + if (vma1 && (vma1->vm_flags & VM_PCUABI_RESERVE) && + vma2 && (vma2->vm_flags & VM_PCUABI_RESERVE) && + vma1->reserv_start == vma2->reserv_start && + vma1->reserv_len == vma2->reserv_len) + return true; + + return false; +} + +#else + +int reserv_vma_insert_entry(struct vm_area_struct *vma, unsigned long start, + unsigned long len, unsigned long perm) +{ + return 0; +} + +unsigned long reserv_range_insert_entry(unsigned long start, unsigned long len, + unsigned long perm) +{ + return start; +} + +bool reserv_vmi_range_fully_mapped(struct vma_iterator *vmi, unsigned long start, + unsigned long len) +{ + return true; +} + +bool reserv_vma_range_fully_mapped(struct vm_area_struct *vma, unsigned long start, + unsigned long len) +{ + return true; +} + +bool reserv_vmi_match_capability(struct vma_iterator *vmi, user_uintptr_t start) +{ + return true; +} + +bool reserv_vma_match_capability(struct vm_area_struct *vma, user_uintptr_t start) +{ + return true; +} + +bool reserv_vma_match_properties(struct vm_area_struct *vma1, struct vm_area_struct *vma2) +{ + return true; +} + +#endif /* CONFIG_CHERI_PURECAP_UABI */