Use the recently introduced PCuABI reservation interfaces for mmap/munmap syscall. The capability returned by mmap syscalls are now bounded and also the range is preserved in reservation layer to perform different range constraint checks in subsequent syscalls.
The kernel internal mapping functions vm_mmap and vm_munmap are also modified to accept user pointers. However, the users of vm_mmap/vm_munmap are not modified and hence the reservation interface might not be completely functional.
Signed-off-by: Amit Daniel Kachhap amit.kachhap@arm.com --- include/linux/cheri.h | 3 ++ include/linux/mm.h | 16 ++++---- mm/mmap.c | 90 ++++++++++++++++++++++++++++++++++++------- mm/util.c | 10 +---- 4 files changed, 91 insertions(+), 28 deletions(-)
diff --git a/include/linux/cheri.h b/include/linux/cheri.h index e5f588b056ad..02ef0e911e63 100644 --- a/include/linux/cheri.h +++ b/include/linux/cheri.h @@ -37,6 +37,9 @@ (CHERI_PERM_GLOBAL | CHERI_PERM_SW_VMEM) #endif
+#define cheri_is_null_derived(cap) \ + cheri_is_equal_exact((uintcap_t)cheri_address_get(cap), cap) + /** * cheri_build_user_cap() - Create a userspace capability. * @addr: Requested capability address. diff --git a/include/linux/mm.h b/include/linux/mm.h index 00cb9fd3a5ee..77e3374c65e0 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3139,16 +3139,18 @@ unsigned long randomize_page(unsigned long start, unsigned long range);
extern unsigned long get_unmapped_area(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
-extern unsigned long mmap_region(struct file *file, unsigned long addr, +extern unsigned long mmap_region(struct file *file, user_uintptr_t addr, unsigned long len, vm_flags_t vm_flags, unsigned long pgoff, - struct list_head *uf); -extern unsigned long do_mmap(struct file *file, unsigned long addr, + struct list_head *uf, unsigned long prot); + +extern user_uintptr_t do_mmap(struct file *file, user_uintptr_t addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate, struct list_head *uf); + extern int do_vmi_munmap(struct vma_iterator *vmi, struct mm_struct *mm, - unsigned long start, size_t len, struct list_head *uf, + user_uintptr_t start, size_t len, struct list_head *uf, bool downgrade); -extern int do_munmap(struct mm_struct *, unsigned long, size_t, +extern int do_munmap(struct mm_struct *, user_uintptr_t, size_t, struct list_head *uf); extern int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int behavior);
@@ -3170,8 +3172,8 @@ static inline void mm_populate(unsigned long addr, unsigned long len) {} /* These take the mm semaphore themselves */ extern int __must_check vm_brk(unsigned long, unsigned long); extern int __must_check vm_brk_flags(unsigned long, unsigned long, unsigned long); -extern int vm_munmap(unsigned long, size_t); -extern unsigned long __must_check vm_mmap(struct file *, unsigned long, +extern int vm_munmap(user_uintptr_t, size_t); +extern user_uintptr_t __must_check vm_mmap(struct file *, user_uintptr_t, unsigned long, unsigned long, unsigned long, unsigned long);
diff --git a/mm/mmap.c b/mm/mmap.c index 74e52dc512fa..173fb75f7122 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -47,7 +47,9 @@ #include <linux/oom.h> #include <linux/sched/mm.h> #include <linux/ksm.h> +#include <linux/cap_addr_mgmt.h>
+#include <linux/cheri.h> #include <linux/uaccess.h> #include <asm/cacheflush.h> #include <asm/tlb.h> @@ -56,7 +58,6 @@ #define CREATE_TRACE_POINTS #include <trace/events/mmap.h>
-#include "cap_addr_mgmt.h" #include "internal.h"
#ifndef arch_mmap_check @@ -1225,7 +1226,7 @@ static inline bool file_mmap_ok(struct file *file, struct inode *inode, /* * The caller must write-lock current->mm->mmap_lock. */ -unsigned long do_mmap(struct file *file, unsigned long addr, +user_uintptr_t do_mmap(struct file *file, user_uintptr_t user_addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate, struct list_head *uf) @@ -1233,6 +1234,14 @@ unsigned long do_mmap(struct file *file, unsigned long addr, struct mm_struct *mm = current->mm; vm_flags_t vm_flags; int pkey = 0; + unsigned long addr = (ptraddr_t)user_addr; + bool new_caps = true; +#ifdef CONFIG_CHERI_PURECAP_UABI + bool ignore_reserv = false; + VMA_ITERATOR(vmi, mm, addr); +#else + bool ignore_reserv = true; +#endif
validate_mm(mm); *populate = 0; @@ -1240,6 +1249,23 @@ unsigned long do_mmap(struct file *file, unsigned long addr, if (!len) return -EINVAL;
+#ifdef CONFIG_CHERI_PURECAP_UABI + if (is_compat_task()) { + ignore_reserv = true; + goto skip_pcuabi_checks; + } + if (cheri_tag_get(user_addr)) { + if (!capability_owns_range(user_addr, addr, len) || !(flags & MAP_FIXED)) + return -EINVAL; + if (!reserv_vmi_range_fully_mapped(&vmi, addr, len)) + return -ERESERVATION; + new_caps = false; + } else { + if (user_addr && !cheri_is_null_derived(user_addr)) + return -EINVAL; + } +skip_pcuabi_checks: +#endif /* * Does the application expect PROT_READ to imply PROT_EXEC? * @@ -1397,11 +1423,26 @@ unsigned long do_mmap(struct file *file, unsigned long addr, vm_flags |= VM_NORESERVE; }
- addr = mmap_region(file, addr, len, vm_flags, pgoff, uf); + if (!ignore_reserv) + vm_flags |= VM_PCUABI_RESERVE; + if (new_caps) + user_addr = addr; + + addr = mmap_region(file, user_addr, len, vm_flags, pgoff, uf, prot); if (!IS_ERR_VALUE(addr) && ((vm_flags & VM_LOCKED) || (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE)) *populate = len; + if (!IS_ERR_VALUE(addr)) { + if (!ignore_reserv) { + if (new_caps) + user_addr = build_owning_capability(addr, len, prot); + } else { + user_addr = (user_uintptr_t)uaddr_to_user_ptr_safe(addr); + } + return user_addr; + } + return addr; }
@@ -2559,11 +2600,13 @@ do_vmi_align_munmap(struct vma_iterator *vmi, struct vm_area_struct *vma, * Returns: -EINVAL on failure, 1 on success and unlock, 0 otherwise. */ int do_vmi_munmap(struct vma_iterator *vmi, struct mm_struct *mm, - unsigned long start, size_t len, struct list_head *uf, + user_uintptr_t user_start, size_t len, struct list_head *uf, bool downgrade) { unsigned long end; struct vm_area_struct *vma; + int ret; + unsigned long start = (ptraddr_t)user_start;
if ((offset_in_page(start)) || start > TASK_SIZE || len > TASK_SIZE-start) return -EINVAL; @@ -2580,7 +2623,16 @@ int do_vmi_munmap(struct vma_iterator *vmi, struct mm_struct *mm, if (!vma) return 0;
- return do_vmi_align_munmap(vmi, vma, mm, start, end, uf, downgrade); + if (vma->vm_flags & VM_PCUABI_RESERVE) { + if (!capability_owns_range(user_start, start, len)) + return -EINVAL; + if (!reserv_vma_match_capability(vma, user_start)) + return -ERESERVATION; + } + + ret = do_vmi_align_munmap(vmi, vma, mm, start, end, uf, downgrade); + + return ret; }
/* do_munmap() - Wrapper function for non-maple tree aware do_munmap() calls. @@ -2589,7 +2641,7 @@ int do_vmi_munmap(struct vma_iterator *vmi, struct mm_struct *mm, * @len: The length to be munmapped. * @uf: The userfaultfd list_head */ -int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, +int do_munmap(struct mm_struct *mm, user_uintptr_t start, size_t len, struct list_head *uf) { VMA_ITERATOR(vmi, mm, start); @@ -2597,15 +2649,16 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, return do_vmi_munmap(&vmi, mm, start, len, uf, false); }
-unsigned long mmap_region(struct file *file, unsigned long addr, +unsigned long mmap_region(struct file *file, user_uintptr_t user_addr, unsigned long len, vm_flags_t vm_flags, unsigned long pgoff, - struct list_head *uf) + struct list_head *uf, unsigned long prot) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma = NULL; struct vm_area_struct *next, *prev, *merge; pgoff_t pglen = len >> PAGE_SHIFT; unsigned long charged = 0; + unsigned long addr = (ptraddr_t)user_addr; unsigned long end = addr + len; unsigned long merge_start = addr, merge_end = end; pgoff_t vm_pgoff; @@ -2628,7 +2681,7 @@ unsigned long mmap_region(struct file *file, unsigned long addr, }
/* Unmap any existing mapping in the area */ - if (do_vmi_munmap(&vmi, mm, addr, len, uf, false)) + if (do_vmi_munmap(&vmi, mm, user_addr, len, uf, false)) return -ENOMEM;
/* @@ -2643,7 +2696,7 @@ unsigned long mmap_region(struct file *file, unsigned long addr,
next = vma_next(&vmi); prev = vma_prev(&vmi); - if (vm_flags & VM_SPECIAL) + if (vm_flags & (VM_SPECIAL | VM_PCUABI_RESERVE)) goto cannot_expand;
/* Attempt to expand an old mapping */ @@ -2693,6 +2746,11 @@ unsigned long mmap_region(struct file *file, unsigned long addr, vm_flags_init(vma, vm_flags); vma->vm_page_prot = vm_get_page_prot(vm_flags); vma->vm_pgoff = pgoff; + if (vm_flags & VM_PCUABI_RESERVE) { + error = reserv_vma_insert_entry(vma, addr, len, prot); + if (error) + goto free_vma; + }
if (file) { if (vm_flags & VM_SHARED) { @@ -2845,17 +2903,18 @@ unsigned long mmap_region(struct file *file, unsigned long addr, return error; }
-static int __vm_munmap(unsigned long start, size_t len, bool downgrade) +static int __vm_munmap(user_uintptr_t user_start, size_t len, bool downgrade) { int ret; struct mm_struct *mm = current->mm; + unsigned long start = (ptraddr_t)user_start; LIST_HEAD(uf); VMA_ITERATOR(vmi, mm, start);
if (mmap_write_lock_killable(mm)) return -EINTR;
- ret = do_vmi_munmap(&vmi, mm, start, len, &uf, downgrade); + ret = do_vmi_munmap(&vmi, mm, user_start, len, &uf, downgrade); /* * Returning 1 indicates mmap_lock is downgraded. * But 1 is not legal return value of vm_munmap() and munmap(), reset @@ -2871,7 +2930,8 @@ static int __vm_munmap(unsigned long start, size_t len, bool downgrade) return ret; }
-int vm_munmap(unsigned long start, size_t len) +/* TODO [PCuABI] - Update the users of vm_munmap */ +int vm_munmap(user_uintptr_t start, size_t len) { return __vm_munmap(start, len, false); } @@ -2879,7 +2939,11 @@ EXPORT_SYMBOL(vm_munmap);
SYSCALL_DEFINE2(munmap, user_uintptr_t, addr, size_t, len) { +#ifdef CONFIG_CHERI_PURECAP_UABI + addr = cheri_address_set(addr, untagged_addr(cheri_address_get(addr))); +#else addr = untagged_addr(addr); +#endif return __vm_munmap(addr, len, true); }
diff --git a/mm/util.c b/mm/util.c index 61de3bf7712b..6f5d9d864643 100644 --- a/mm/util.c +++ b/mm/util.c @@ -540,24 +540,18 @@ user_uintptr_t vm_mmap_pgoff(struct file *file, user_uintptr_t addr, if (!ret) { if (mmap_write_lock_killable(mm)) return -EINTR; - /* - * TODO [PCuABI] - might need propagating uintcap further down - * to do_mmap to properly handle capabilities - */ ret = do_mmap(file, addr, len, prot, flag, pgoff, &populate, &uf); mmap_write_unlock(mm); userfaultfd_unmap_complete(mm, &uf); if (populate) mm_populate(ret, populate); - /* TODO [PCuABI] - derive proper capability */ - if (!IS_ERR_VALUE(ret)) - ret = (user_uintptr_t)uaddr_to_user_ptr_safe((ptraddr_t)ret); } return ret; }
-unsigned long vm_mmap(struct file *file, unsigned long addr, +/* TODO [PCuABI] - Update the users of vm_mmap */ +user_uintptr_t vm_mmap(struct file *file, user_uintptr_t addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset) {