Use the recently introduced PCuABI reservation interfaces and add the relevant capability/reservation constraint checks on the different mremap syscall parameters.
Modify vma_expandable() to accept input address as capability pointer since get_unmapped_area() now requires it for MMAP_FIXED type mappings.
Signed-off-by: Amit Daniel Kachhap amitdaniel.kachhap@arm.com --- mm/mremap.c | 93 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 17 deletions(-)
diff --git a/mm/mremap.c b/mm/mremap.c index b52592303e8b..2e2955417730 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -25,6 +25,7 @@ #include <linux/uaccess.h> #include <linux/userfaultfd_k.h> #include <linux/mempolicy.h> +#include <linux/cap_addr_mgmt.h>
#include <asm/cacheflush.h> #include <asm/tlb.h> @@ -785,16 +786,19 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr, return vma; }
-static unsigned long mremap_to(unsigned long addr, unsigned long old_len, - unsigned long new_addr, unsigned long new_len, bool *locked, +static user_uintptr_t mremap_to(user_uintptr_t usrptr, unsigned long old_len, + user_uintptr_t new_usrptr, unsigned long new_len, bool *locked, unsigned long flags, struct vm_userfaultfd_ctx *uf, struct list_head *uf_unmap_early, struct list_head *uf_unmap) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma; - unsigned long ret = -EINVAL; + user_uintptr_t ret = -EINVAL; unsigned long map_flags = 0; + ptraddr_t addr = (ptraddr_t)usrptr; + ptraddr_t new_addr = (ptraddr_t)new_usrptr; + unsigned long old_perm = 0;
if (offset_in_page(new_addr)) goto out; @@ -865,22 +869,38 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len, if (!(flags & MREMAP_FIXED)) new_addr = ret;
+#ifdef CONFIG_CHERI_PURECAP_UABI + old_perm = vma->reserv_perm; +#endif ret = move_vma(vma, addr, old_len, new_len, new_addr, locked, flags, uf, uf_unmap);
+ if (!IS_ERR_VALUE(ret)) { + if (test_bit(MMF_PCUABI_RESERVE, &mm->flags)) { + if (!(flags & MREMAP_FIXED)) + ret = build_owning_capability(new_addr, new_len, old_perm); + else + ret = new_usrptr; + } + } out: return ret; }
-static int vma_expandable(struct vm_area_struct *vma, unsigned long delta) +static int vma_expandable(user_uintptr_t usrptr, struct vm_area_struct *vma, unsigned long delta) { unsigned long end = vma->vm_end + delta;
+#ifdef CONFIG_CHERI_PURECAP_UABI + usrptr = cheri_address_set(usrptr, vma->vm_start); +#else + usrptr = vma->vm_start; +#endif if (end < vma->vm_end) /* overflow */ return 0; if (find_vma_intersection(vma->vm_mm, vma->vm_end, end)) return 0; - if (get_unmapped_area(NULL, vma->vm_start, end - vma->vm_start, + if (get_unmapped_area(NULL, usrptr, end - vma->vm_start, 0, MAP_FIXED) & ~PAGE_MASK) return 0; return 1; @@ -893,9 +913,9 @@ static int vma_expandable(struct vm_area_struct *vma, unsigned long delta) * MREMAP_FIXED option added 5-Dec-1999 by Benjamin LaHaise * This option implies MREMAP_MAYMOVE. */ -SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len, +SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, usrptr, unsigned long, old_len, unsigned long, new_len, unsigned long, flags, - user_uintptr_t, new_addr) + user_uintptr_t, new_usrptr) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma; @@ -903,10 +923,15 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len bool locked = false; bool downgraded = false; struct vm_userfaultfd_ctx uf = NULL_VM_UFFD_CTX; + ptraddr_t addr = (ptraddr_t)usrptr; + ptraddr_t new_addr = (ptraddr_t)new_usrptr; + unsigned long old_perm = 0; LIST_HEAD(uf_unmap_early); LIST_HEAD(uf_unmap); +#ifdef CONFIG_CHERI_PURECAP_UABI + VMA_ITERATOR(vmi, current->mm, new_addr); +#endif
- /* @TODO [PCuABI] - capability validation */ /* * There is a deliberate asymmetry here: we strip the pointer tag * from the old address but leave the new address alone. This is @@ -918,6 +943,9 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len * See Documentation/arm64/tagged-address-abi.rst for more information. */ addr = untagged_addr(addr); +#ifdef CONFIG_CHERI_PURECAP_UABI + usrptr = cheri_address_set(usrptr, addr); +#endif
if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP)) return ret; @@ -956,6 +984,34 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len goto out; }
+#ifdef CONFIG_CHERI_PURECAP_UABI + if (!test_bit(MMF_PCUABI_RESERVE, &mm->flags)) + goto skip_pcuabi_checks; + if (!capability_owns_range(usrptr, addr, old_len ? old_len : new_len)) + goto out; + if (!reserv_vma_valid_capability(vma, usrptr)) { + ret = -ERESERVATION; + goto out; + } + if ((flags & MREMAP_FIXED)) { + if (!capability_owns_range(new_usrptr, new_addr, new_len)) + goto out; + if (!reserv_vmi_valid_capability(&vmi, new_usrptr, true)) { + ret = -ERESERVATION; + goto out; + } + if (!reserv_vmi_range_mapped(&vmi, new_addr, new_len, true)) { + ret = -ENOMEM; + goto out; + } + } else { + if (!cheri_is_null_derived(new_usrptr)) + goto out; + } + old_perm = vma->reserv_perm; +skip_pcuabi_checks: +#endif + if (is_vm_hugetlb_page(vma)) { struct hstate *h __maybe_unused = hstate_vma(vma);
@@ -977,7 +1033,7 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len }
if (flags & (MREMAP_FIXED | MREMAP_DONTUNMAP)) { - ret = mremap_to(addr, old_len, new_addr, new_len, + ret = mremap_to(usrptr, old_len, new_usrptr, new_len, &locked, flags, &uf, &uf_unmap_early, &uf_unmap); goto out; @@ -1003,7 +1059,7 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len goto out; }
- ret = addr; + ret = usrptr; goto out; }
@@ -1020,7 +1076,7 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len */ if (old_len == vma->vm_end - addr) { /* can we just expand the current mapping? */ - if (vma_expandable(vma, new_len - old_len)) { + if (vma_expandable(usrptr, vma, new_len - old_len)) { long pages = (new_len - old_len) >> PAGE_SHIFT; unsigned long extension_start = addr + old_len; unsigned long extension_end = addr + new_len; @@ -1059,7 +1115,7 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len locked = true; new_addr = addr; } - ret = addr; + ret = usrptr; goto out; } } @@ -1083,8 +1139,14 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len goto out; }
- ret = move_vma(vma, addr, old_len, new_len, new_addr, + ret = move_vma(vma, usrptr, old_len, new_len, new_addr, &locked, flags, &uf, &uf_unmap); + if (!IS_ERR_VALUE(ret)) { + if (test_bit(MMF_PCUABI_RESERVE, &mm->flags)) + ret = build_owning_capability(new_addr, new_len, old_perm); + else + ret = (user_uintptr_t)new_addr; + } } out: if (offset_in_page(ret)) @@ -1098,8 +1160,5 @@ SYSCALL_DEFINE5(__retptr__(mremap), user_uintptr_t, addr, unsigned long, old_len userfaultfd_unmap_complete(mm, &uf_unmap_early); mremap_userfaultfd_complete(&uf, addr, ret, old_len); userfaultfd_unmap_complete(mm, &uf_unmap); - /* TODO [PCuABI] - derive proper capability */ - return IS_ERR_VALUE(ret) ? - ret : - (user_intptr_t)uaddr_to_user_ptr_safe((ptraddr_t)ret); + return ret; }