When a capability is created it is assigned the maximum permissions it may ever have by passing PROT_MAX(max_prot) as one of the prot flags. Any attempt to increase the permissions beyond this would result in failure of the syscall. An owning capability returned by mmap() does not include LoadCap and StoreCap permissions if the mapping is shared. A check to verify the same has been added.
mremap() doesn't take a prot argument and retains the permissions of the original capability. If the permissions of the new capability do not match the set of permissions of the old capabaility, the syscall fails with a -EINVAL error code. Tests to verify the above behaviour have been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 130 +++++++++++++++++++ 1 file changed, 130 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 645d6b977bdf..5c6c8e8efd75 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -15,6 +15,7 @@ #define MMAP_SIZE ((1ULL << 16) << 1) /* 64k x 2 */ #define MMAP_SIZE_REDUCED (MMAP_SIZE >> 1) #define FILE_PERM 0666 +#define PROT_ALL (PROT_READ | PROT_WRITE | PROT_EXEC)
#define PROBE_MODE_TOUCH 0x01 #define PROBE_MODE_VERIFY 0x02 @@ -465,6 +466,134 @@ TEST(test_mremap_bounds_check) ASSERT_EQ(retval, 0); }
+/* test to verify mremap() behaviour when permissions are modified */ +TEST(test_permissions) +{ + void *addr, *ptr, *old_ptr, *new_ptr, *ret; + int flags, retval; + int prot, max_prot; + size_t perms, old_perms, new_perms; + + addr = (void *)(uintcap_t)pagesize; + + /* increase permission beyond the maximum prot specified for the mapping */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + max_prot = PROT_READ | PROT_WRITE; + prot = PROT_READ; + ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(max_prot) | prot, + flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(ptr)); + retval = mprotect(ptr, MMAP_SIZE, PROT_EXEC); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* max_prot has fewer permissions than prot */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + max_prot = PROT_WRITE | PROT_EXEC; + prot = PROT_ALL; + ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(max_prot) | prot, flags, -1, 0); + VERIFY_ERRNO((unsigned long)ptr, (unsigned long)-EINVAL); + + /* max_prot has more permissions than prot */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + max_prot = PROT_ALL; + prot = PROT_READ | PROT_EXEC; + ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(max_prot) | prot, flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(ptr)); + + retval = mprotect(ptr, MMAP_SIZE, PROT_WRITE); + ASSERT_EQ(retval, 0); + EXPECT_EQ(0, probe_mem_range(ptr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* repeat positive max_prot test with fixed address */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + max_prot = PROT_ALL; + prot = PROT_READ | PROT_EXEC; + ptr = mmap(addr, MMAP_SIZE, PROT_MAX(max_prot) | prot, + flags | MAP_FIXED, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(ptr)); + + retval = mprotect(ptr, MMAP_SIZE, PROT_WRITE); + ASSERT_EQ(retval, 0); + + retval = munmap(ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* LoadCap and StoreCap permissions must not be given to a shared mapping */ + flags = MAP_SHARED | MAP_ANONYMOUS; + prot = PROT_READ | PROT_WRITE; + ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(prot) | PROT_READ, flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(ptr)); + perms = cheri_perms_get(ptr); + retval = ((perms & CHERI_PERM_LOAD) && (perms & CHERI_PERM_STORE_CAP)); + ASSERT_EQ(retval, 0); + + retval = munmap(ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* permissions of capability returned by mremap must match the + * permissions returned by the original mapping + */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + prot = PROT_READ | PROT_WRITE; + old_ptr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(old_ptr)); + old_perms = cheri_perms_get(old_ptr); + new_ptr = mremap(old_ptr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE, 0); + ASSERT_FALSE(IS_ERR_VALUE(new_ptr)); + new_perms = cheri_perms_get(new_ptr); + ASSERT_EQ(old_perms, new_perms); + EXPECT_EQ(0, probe_mem_range(new_ptr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(new_ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* remapping to a new_ptr having reduced permissions from old_ptr */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + prot = PROT_READ | PROT_WRITE; + old_ptr = mmap(NULL, MMAP_SIZE_REDUCED, PROT_MAX(prot | PROT_EXEC) | + prot, flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(old_ptr)); + + new_ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(prot) | PROT_READ, flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(new_ptr)); + ret = mremap(old_ptr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE | MREMAP_FIXED, new_ptr); + ASSERT_FALSE(IS_ERR_VALUE(ret)); + EXPECT_EQ(0, probe_mem_range(new_ptr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + retval = munmap(ret, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* remapping to new_ptr having increased permissions from old_ptr */ + flags = MAP_PRIVATE | MAP_ANONYMOUS; + prot = PROT_READ | PROT_WRITE; + old_ptr = mmap(NULL, MMAP_SIZE_REDUCED, PROT_MAX(prot) | PROT_READ, + flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(old_ptr)); + + new_ptr = mmap(NULL, MMAP_SIZE, PROT_MAX(prot | PROT_EXEC) | prot, + flags, -1, 0); + ASSERT_FALSE(IS_ERR_VALUE(new_ptr)); + + ret = mremap(old_ptr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE | MREMAP_FIXED, new_ptr); + VERIFY_ERRNO((unsigned long)ret, (unsigned long)-EINVAL); + + retval = munmap(new_ptr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + retval = munmap(old_ptr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); +} + int main(int argc, char **argv, char **envp, struct morello_auxv *auxv) { reg_data.argc = argc; @@ -481,5 +610,6 @@ int main(int argc, char **argv, char **envp, struct morello_auxv *auxv) test_range_check(); test_mmap_bounds_check(); test_mremap_bounds_check(); + test_permissions(); return 0; }