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.
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 | 10 +++--- mm/mmap.c | 81 +++++++++++++++++++++++++++++++++++++++---- mm/util.c | 10 ++---- 4 files changed, 84 insertions(+), 20 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 1b32c2b81464..4206b761d777 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3140,16 +3140,16 @@ extern unsigned long get_unmapped_area(struct file *, unsigned long, unsigned lo extern unsigned long mmap_region(struct file *file, unsigned long 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, +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, bool reserve_ignore); extern int do_munmap(struct mm_struct *, unsigned long, size_t, struct list_head *uf); -extern int do_munmap_use_reserv(struct mm_struct *mm, unsigned long start, size_t len, +extern int do_munmap_use_reserv(struct mm_struct *mm, user_uintptr_t start, size_t len, struct list_head *uf); extern int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int behavior);
@@ -3171,8 +3171,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 f4a9099365bf..8f9c3d8686ab 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -48,6 +48,8 @@ #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> @@ -1224,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) @@ -1232,6 +1234,11 @@ 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; +#ifdef CONFIG_CHERI_PURECAP_UABI + bool is_reservation = false; + int ret; +#endif
validate_mm(mm); *populate = 0; @@ -1239,6 +1246,20 @@ unsigned long do_mmap(struct file *file, unsigned long addr, if (!len) return -EINVAL;
+#ifdef CONFIG_CHERI_PURECAP_UABI + if (cheri_tag_get(user_addr)) { + if (!capability_owns_range(user_addr, addr, len)) + return -EINVAL; + if (!reserv_mt_range_fully_mapped(&mm->reserv_mt, addr, len) || + !reserv_mt_capability_bound_valid(&mm->reserv_mt, user_addr)) + return -ERESERVATION; + } else { + if (!cheri_is_null_derived(user_addr)) + return -EINVAL; + is_reservation = true; + } +#endif /* CONFIG_CHERI_PURECAP_UABI */ + /* * Does the application expect PROT_READ to imply PROT_EXEC? * @@ -1396,11 +1417,35 @@ unsigned long do_mmap(struct file *file, unsigned long addr, vm_flags |= VM_NORESERVE; }
+#ifdef CONFIG_CHERI_PURECAP_UABI + if (is_reservation) { + /* + * Check if there is any overlap with the existing reservation. + * This may help in filtering out any reservation error before + * the actual memory mapping. + */ + if (reserv_mt_range_valid(&mm->reserv_mt, addr, len)) + return -ERESERVATION; + } +#endif addr = mmap_region(file, addr, len, vm_flags, pgoff, uf); if (!IS_ERR_VALUE(addr) && ((vm_flags & VM_LOCKED) || (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE)) *populate = len; +#ifdef CONFIG_CHERI_PURECAP_UABI + if (!IS_ERR_VALUE(addr)) { + if (is_reservation) { + ret = reserv_mt_insert_entry(&mm->reserv_mt, addr, len, prot); + if (ret) + return ret; + user_addr = build_owning_capability(addr, len, prot); + } else { + user_addr = cheri_address_set(user_addr, addr); + } + return user_addr; + } +#endif return addr; }
@@ -2510,11 +2555,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, bool reserve_ignore) { 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; @@ -2531,7 +2578,21 @@ 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); +#ifdef CONFIG_CHERI_PURECAP_UABI + if (!reserve_ignore) { + if (!capability_owns_range(user_start, start, len)) + return -EINVAL; + if (!reserv_mt_capability_bound_valid(&mm->reserv_mt, user_start)) + return -ERESERVATION; + } +#endif + + ret = do_vmi_align_munmap(vmi, vma, mm, start, end, uf, downgrade); +#ifdef CONFIG_CHERI_PURECAP_UABI + if (!reserve_ignore && ret >= 0) + reserv_mt_delete_range(&mm->reserv_mt, start, len); +#endif + return ret; }
/* do_munmap() - Wrapper function for non-maple tree aware do_munmap() calls. @@ -2555,7 +2616,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len, * @len: The length to be munmapped. * @uf: The userfaultfd list_head */ -int do_munmap_use_reserv(struct mm_struct *mm, unsigned long start, size_t len, +int do_munmap_use_reserv(struct mm_struct *mm, user_uintptr_t start, size_t len, struct list_head *uf) { VMA_ITERATOR(vmi, mm, start); @@ -2812,17 +2873,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, true); + ret = do_vmi_munmap(&vmi, mm, user_start, len, &uf, downgrade, false); /* * Returning 1 indicates mmap_lock is downgraded. * But 1 is not legal return value of vm_munmap() and munmap(), reset @@ -2838,7 +2900,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); } @@ -2846,7 +2909,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) {