From: Amit Daniel Kachhap amitdaniel.kachhap@arm.com
Capability permissions and bounds constraints are added as per the PCuABI specification for the mincore syscall. mincore() does not require VMem and only one of the RWX permissions, so the standard check_user_ptr_owning() interface is not used, and permissions are verified explicitly.
Also, as mincore() allows the address range to not span whole pages, the bounds checking is tweaked accordingly.
Signed-off-by: Amit Daniel Kachhap amitdaniel.kachhap@arm.com Co-developed-by: Kevin Brodsky kevin.brodsky@arm.com Signed-off-by: Kevin Brodsky kevin.brodsky@arm.com --- mm/mincore.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-)
diff --git a/mm/mincore.c b/mm/mincore.c index dd164cb84ba8..e59a877e3e99 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -19,6 +19,7 @@ #include <linux/hugetlb.h> #include <linux/pgtable.h>
+#include <linux/mm_reserv.h> #include <linux/uaccess.h> #include "swap.h"
@@ -172,6 +173,50 @@ static inline bool can_do_mincore(struct vm_area_struct *vma) file_permission(vma->vm_file, MAY_WRITE) == 0; }
+static int check_pcuabi_mincore_ptr_arg(user_uintptr_t user_ptr, + unsigned long start, + unsigned long len) +{ +#ifdef CONFIG_CHERI_PURECAP_UABI + ptraddr_t cap_base, min_upper_bound; + size_t cap_len; + cheri_perms_t perms; + + if (!reserv_is_supported(current->mm)) + return 0; + + cap_base = cheri_base_get(user_ptr); + cap_len = cheri_length_get(user_ptr); + perms = cheri_perms_get(user_ptr); + + /* + * mincore does not require the VMem permission so as to allow ordinary + * pointers (unlike other address space management syscalls requiring an + * owning capability). + * Requiring at least one of the standard memory permissions RWX will + * however help to reject non-memory capabilities. + */ + if (!(cheri_is_valid(user_ptr) && cheri_is_unsealed(user_ptr) && + (perms & CHERI_PERM_GLOBAL) && + (perms & (CHERI_PERM_LOAD | CHERI_PERM_STORE | CHERI_PERM_EXECUTE)))) + return -EINVAL; + + /* + * mincore syscall can be invoked as: + * mincore(align_down(p, PAGE_SIZE), sz + (p.addr % PAGE_SIZE), vec) + * Hence, the capability bounds may not encompass the page-aligned range. + * For that reason, we only require that the capability bounds cover at + * least one byte in the first and last page. + */ + min_upper_bound = start + PAGE_ALIGN_DOWN(len) + + (offset_in_page(len) > 0 ? 1 : 0); + if (!((start + PAGE_SIZE > cap_base) && + (cap_base + cap_len >= min_upper_bound))) + return -EINVAL; +#endif + return 0; +} + static const struct mm_walk_ops mincore_walk_ops = { .pmd_entry = mincore_pte_range, .pte_hole = mincore_unmapped_range, @@ -229,14 +274,13 @@ static long do_mincore(unsigned long addr, unsigned long pages, unsigned char *v * mapped * -EAGAIN - A kernel resource was temporarily unavailable. */ -SYSCALL_DEFINE3(mincore, unsigned long, start, size_t, len, +SYSCALL_DEFINE3(mincore, user_uintptr_t, user_ptr, size_t, len, unsigned char __user *, vec) { long retval; unsigned long pages; unsigned char *tmp; - - start = untagged_addr(start); + unsigned long start = untagged_addr((ptraddr_t)user_ptr);
/* Check the start address: needs to be page-aligned.. */ if (start & ~PAGE_MASK) @@ -253,6 +297,10 @@ SYSCALL_DEFINE3(mincore, unsigned long, start, size_t, len, if (!access_ok(vec, pages)) return -EFAULT;
+ retval = check_pcuabi_mincore_ptr_arg(user_ptr, start, len); + if (retval) + return retval; + tmp = (void *) __get_free_page(GFP_USER); if (!tmp) return -EAGAIN;