Syscalls operating on memory mappings manage their address space via owning capabilities. They must adhere to a certain set of rules[1] in order to ensure memory safety. Address space management syscalls are only allowed to manipulate mappings that are within the range of the owning capability and have the appropriate permissions. Tests to vailidate the parameters being passed to the syscall, check its bounds, range as well as permissions have been added. Additionally, a signal handler has been registered to handle invalid memory access. Finally, as certain flags and syscalls conflict with the reservation model or lack implementation, a check to verify appropriate handling of the same has also been added.
This patch series has been tested on: https://git.morello-project.org/amitdaniel/linux/-/tree/review/extern_reserv...
[1] https://git.morello-project.org/morello/kernel/linux/-/wikis/Morello-pure-ca...
Chaitanya S Prakash (8): kselftests/arm64/morello: Add necessary support for mmap testcases kselftests/arm64/morello: Add MAP_GROWSDOWN testcase kselftests/arm64/morello: Add parameter check testcases kselftests/arm64/morello: Add capability range testcases kselftests/arm64/morello: Add mmap() bounds check testcases kselftests/arm64/morello: Add mremap() bounds check testcases kselftests/arm64/morello: Add mremap() permission testcases kselftests/arm64/morello: Add brk() testcase
.../testing/selftests/arm64/morello/Makefile | 1 + .../selftests/arm64/morello/freestanding.h | 62 ++- tools/testing/selftests/arm64/morello/mmap.c | 480 +++++++++++++++++- 3 files changed, 536 insertions(+), 7 deletions(-)
Wrapper functions for system calls that are frequently invoked are added in order to improve ease of usability. Making use of the newly added support, mmap.c can be re-written in a manner that avoids directly interacting with syscalls. Required datatypes and constants have also been defined. Additionally, dependency required to utilise signal support has also been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- .../testing/selftests/arm64/morello/Makefile | 1 + .../selftests/arm64/morello/freestanding.h | 62 ++++++++++++++++++- tools/testing/selftests/arm64/morello/mmap.c | 11 ++-- 3 files changed, 67 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/arm64/morello/Makefile b/tools/testing/selftests/arm64/morello/Makefile index ecfa6fc5e989..d2e100a5aadc 100644 --- a/tools/testing/selftests/arm64/morello/Makefile +++ b/tools/testing/selftests/arm64/morello/Makefile @@ -43,3 +43,4 @@ $(OUTPUT)/%: $(OUTPUT)/%.o $(OUTPUT)/freestanding_start.o $(OUTPUT)/freestanding
$(OUTPUT)/signal: $(OUTPUT)/signal_common.o $(OUTPUT)/clone: $(OUTPUT)/signal_common.o +$(OUTPUT)/mmap: $(OUTPUT)/signal_common.o diff --git a/tools/testing/selftests/arm64/morello/freestanding.h b/tools/testing/selftests/arm64/morello/freestanding.h index 2beb52eafae1..3badf163569c 100644 --- a/tools/testing/selftests/arm64/morello/freestanding.h +++ b/tools/testing/selftests/arm64/morello/freestanding.h @@ -21,6 +21,9 @@ typedef __kernel_timer_t timer_t; typedef __kernel_clockid_t clockid_t; typedef __kernel_uid_t uid_t; typedef __kernel_mode_t mode_t; +typedef __kernel_off_t off_t; +typedef __kernel_key_t key_t; + #ifndef __clang__ typedef __uintcap_t uintcap_t; #endif @@ -29,6 +32,7 @@ typedef __uintcap_t uintcap_t; #define INT_MAX __INT_MAX__ #endif
+#define PAGE_SIZE 4096 #define EXIT_SUCCESS 0
#ifndef WIFEXITED @@ -160,6 +164,16 @@ static inline ssize_t write(int fd, const void *buf, size_t count) return syscall(__NR_write, fd, buf, count); }
+static inline off_t lseek(int fd, off_t offset, int whence) +{ + return syscall(__NR_lseek, fd, offset, whence); +} + +static inline int openat(int dirfd, const char *pathname, int flags, mode_t mode) +{ + return syscall(__NR_openat, dirfd, pathname, flags, mode); +} + long __clone(int (*fn)(void *), uintcap_t stack, int flags, void *arg, pid_t *parent_tid, void *tls, pid_t *child_tid);
@@ -174,8 +188,49 @@ static inline int munmap(void *addr, size_t length) return syscall(__NR_munmap, addr, length); }
+static inline int madvise(void *addr, size_t length, int advise) +{ + return syscall(__NR_madvise, addr, length, advise); +} + +static inline int mincore(void *addr, size_t length, unsigned char *vec) +{ + return syscall(__NR_mincore, addr, length, vec); +} + +static inline int mlock(const void *addr, size_t len) +{ + return syscall(__NR_mlock, addr, len); +} + +static inline int mlock2(const void *addr, size_t len, unsigned int flags) +{ + return syscall(__NR_mlock2, addr, len, flags); +} + +static inline int munlock(const void *addr, size_t len) +{ + return syscall(__NR_munlock, addr, len); +} + +static inline int msync(void *addr, size_t length, int flags) +{ + return syscall(__NR_msync, addr, length, flags); +} + +static inline int mprotect(void *addr, size_t length, int prot) +{ + return syscall(__NR_mprotect, addr, length, prot); +} + +static inline void *mremap(void *old_address, size_t old_size, size_t new_size, + int flags, void *new_address) +{ + return (void *)syscall(__NR_mremap, old_address, old_size, new_size, flags, new_address); +} + static inline void *mmap_verified(void *addr, size_t length, int prot, int flags, - int fd, int offset, unsigned int perms) + int fd, int offset, unsigned int perms) { void *__addr = mmap(addr, length, prot, flags, fd, offset);
@@ -200,6 +255,11 @@ static inline void *mmap_verified(void *addr, size_t length, int prot, int flags return NULL; }
+static inline int brk(void *addr) +{ + return syscall(__NR_brk, addr); +} + static inline int close(int fd) { return syscall(__NR_close, fd); diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 5d259c336f55..2dd4ccdb0d2a 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -71,11 +71,11 @@ void syscall_mmap2(void) int retval;
/* create a sample file to map onto with mmap */ - fd = syscall(__NR_openat, 0, sample_file, O_RDWR | O_CREAT, FILE_PERM); + fd = openat(0, sample_file, O_RDWR | O_CREAT, FILE_PERM);
ASSERT_GE(fd, 0);
- retval = syscall(__NR_lseek, fd, MMAP_SIZE, SEEK_SET); + retval = lseek(fd, MMAP_SIZE, SEEK_SET); ASSERT_EQ(retval, MMAP_SIZE);
/* attempt to write arbitrary data to file */ @@ -92,18 +92,17 @@ void syscall_mmap2(void) PROBE_MODE_TOUCH | PROBE_MODE_VERIFY));
/* Attempt to change bounds of memory mapping, shrink by factor of 2 */ - addr = (void *)syscall(__NR_mremap, addr, MMAP_SIZE, - MMAP_SIZE_REDUCED, 0, 0); + addr = mremap(addr, MMAP_SIZE, MMAP_SIZE_REDUCED, 0, 0);
ASSERT_FALSE(IS_ERR_VALUE(addr)); /* advise kernel about how to handle paging of mapped memory.*/ - retval = syscall(__NR_madvise, addr, MMAP_SIZE_REDUCED, MADV_WILLNEED); + retval = madvise(addr, MMAP_SIZE_REDUCED, MADV_WILLNEED); ASSERT_EQ(retval, 0);
EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE_REDUCED, PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); /* An attempt to change permissions to RO */ - retval = syscall(__NR_mprotect, addr, MMAP_SIZE_REDUCED, PROT_READ); + retval = mprotect(addr, MMAP_SIZE_REDUCED, PROT_READ); ASSERT_EQ(retval, 0); /* Write permission should be revoked - verify mode only */ /* To be extended when signals are fully supported */
The mmap() system call is expected to fail with -EOPNOTSUPP when the MAP_GROWSDOWN flag is passed. A testcase to verify this behaviour has been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 2dd4ccdb0d2a..00d4c5ea9703 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -117,6 +117,17 @@ void syscall_mmap2(void) close(fd); }
+/* test to verify mmap() behaviour when MAP_GROWSDOWN flag is specified */ +static void purecap_map_growsdown(void) +{ + void *addr; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN; + + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_EQ((unsigned long)addr, (unsigned long)-EOPNOTSUPP); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -127,9 +138,15 @@ TEST(test_syscall_mmap2) syscall_mmap2(); }
+TEST(test_purecap_map_growsdown) +{ + purecap_map_growsdown(); +} + int main(void) { test_syscall_mmap(); test_syscall_mmap2(); + test_purecap_map_growsdown(); return 0; }
Only valid owning capabilities have the ability to modify an address space by making use of the appropriate syscalls, any deviation from this method must result in immediate failure of the syscall. 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.
A test to validate the parmeters passed to address space management syscalls has been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 76 ++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 00d4c5ea9703..184dcf4ddc92 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -128,6 +128,76 @@ static void purecap_map_growsdown(void) ASSERT_EQ((unsigned long)addr, (unsigned long)-EOPNOTSUPP); }
+ +/* test to validate parameters passed to address space management syscalls */ +static inline void purecap_param_check(void) +{ + void *addr, *addr_cap, *null_cap_addr; + ptraddr_t address; + int retval; + int max_prot = PROT_READ | PROT_WRITE | PROT_EXEC; + int prot = max_prot; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + /* generate null-derived capability to be used later*/ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + address = cheri_address_get(addr); + null_cap_addr = (void *)(uintptr_t)address; + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* passing invalid capability to sycall */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + addr_cap = cheri_tag_clear(addr); + retval = munmap(addr_cap, MMAP_SIZE); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* increase permission beyond the maximum prot specified for the mapping */ + addr = mmap(NULL, MMAP_SIZE, PROT_MAX(PROT_READ | PROT_WRITE) | + PROT_READ | PROT_WRITE, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = mprotect(addr, MMAP_SIZE, PROT_EXEC); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* max_prot has fewer permissions than prot */ + max_prot = (prot & ~(PROT_READ)); + addr = mmap(NULL, MMAP_SIZE, PROT_MAX(max_prot) | prot, flags, -1, 0); + ASSERT_EQ((unsigned long)addr, (unsigned long)-EINVAL); + + /* max_prot has more permissions than prot */ + max_prot = prot; + prot = (prot & ~(PROT_WRITE)); + addr = mmap(NULL, MMAP_SIZE, PROT_MAX(max_prot) | prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + + retval = mprotect(addr, MMAP_SIZE, PROT_WRITE); + ASSERT_NE(retval, -EINVAL); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* repeat positive max_prot test with null-derived capability */ + addr = mmap(null_cap_addr, MMAP_SIZE, PROT_MAX(max_prot) | prot, + flags | MAP_FIXED, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + + retval = mprotect(addr, MMAP_SIZE, PROT_WRITE); + ASSERT_EQ(retval, 0); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -143,10 +213,16 @@ TEST(test_purecap_map_growsdown) purecap_map_growsdown(); }
+TEST(test_purecap_param_check) +{ + purecap_param_check(); +} + int main(void) { test_syscall_mmap(); test_syscall_mmap2(); test_purecap_map_growsdown(); + test_purecap_param_check(); return 0; }
The length of the initial mapping using an mmap() call has the ability to be reduced in a subsequent mmap() call without first being unmapped. Whereas, trying to increase the length of the initial mapping would result in failure as the capability is not permitted to access a range that it does not own.
Address space management syscalls that manipulate a given mapping are restricted to the range owned by the capability. Any attempt to modify or access memory beyond this range will result in failure of syscall.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 132 +++++++++++++++++++ 1 file changed, 132 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 184dcf4ddc92..4a2a552d47dc 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -198,6 +198,132 @@ static inline void purecap_param_check(void) ASSERT_EQ(retval, 0); }
+/* test to verify address space management syscall behaviour when capability + * range is modified */ +static inline void purecap_range_check(void) +{ + void *addr, *ret; + int retval; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + unsigned char vec[MMAP_SIZE / PAGE_SIZE]; + + /* mapping a smaller range at prev mmap addr in a subsequent mmap() + * call without first unmapping */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + ret = mmap(addr, MMAP_SIZE_REDUCED, prot, flags | MAP_FIXED, -1, 0); + ASSERT_GT((unsigned long)ret, 0); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE_REDUCED, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* mapping a larger range at prev mmap addr in a subsequent mmap() + * call without first unmapping */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + ret = mmap(addr, 2 * MMAP_SIZE, prot, flags | MAP_FIXED, -1, 0); + ASSERT_EQ((unsigned long)ret, (unsigned long)-EINVAL); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* positive madvise() range test */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = madvise(addr, MMAP_SIZE, MADV_WILLNEED); + ASSERT_NE(retval, -EINVAL); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* negative madvise() range test */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = madvise(addr, MMAP_SIZE, MADV_NORMAL); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* positive mincore() range test */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = mincore(addr, MMAP_SIZE, vec); + ASSERT_EQ(retval, 0); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* negative mincore() range test */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = mincore(addr, MMAP_SIZE, vec); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* positive mlock() range test */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = mlock(addr, MMAP_SIZE); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + retval = munlock(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* negative mlock() range test */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = mlock(addr, MMAP_SIZE); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* negative munlock() range test */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + EXPECT_EQ(0, mlock2(addr, MMAP_SIZE_REDUCED, MLOCK_ONFAULT)); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE_REDUCED, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munlock(addr, MMAP_SIZE); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* positive msync() range test */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = msync(addr, MMAP_SIZE, MS_SYNC); + ASSERT_EQ(retval, 0); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* negative msync() range test */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + retval = msync(addr, MMAP_SIZE, MS_SYNC); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -218,11 +344,17 @@ TEST(test_purecap_param_check) purecap_param_check(); }
+TEST(test_purecap_range_check) +{ + purecap_range_check(); +} + int main(void) { test_syscall_mmap(); test_syscall_mmap2(); test_purecap_map_growsdown(); test_purecap_param_check(); + test_purecap_range_check(); return 0; }
Reservations are contiguous ranges of virtual addresses that exactly match the bounds of an owning capability which is provided by the kernel. When an owning capability is passed to a syscall, its bounds are first verified against the existing reservations. If the bounds of the capability are found to not match any of the existing reservations, the syscall is expected to fail with a -ERESERVATION error code. Tests to verify this behaviour has been added.
An additional check to verify whether the representable length returned by the capability is greater than or equal to the bounds set for the capability has been added. Finally, signal handler has also been registered to handle invalid memory access.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 116 +++++++++++++++++++ 1 file changed, 116 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 4a2a552d47dc..c7f1ddb395c5 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -8,8 +8,13 @@ #include <linux/errno.h> #include <linux/fs.h> #include <linux/fcntl.h> +#include <linux/signal.h> +#include <asm/sigcontext.h> +#include <asm/siginfo.h> +#include <asm/ucontext.h> #include <cheriintrin.h> #include "freestanding.h" +#include "signal_common.h"
#define MMAP_SIZE ((1ULL << 16) << 1) /* 64k x 2 */ @@ -19,6 +24,7 @@ #define PROBE_MODE_TOUCH 0x01 #define PROBE_MODE_VERIFY 0x02
+static volatile unsigned int signal_status;
static inline int probe_mem_range(void *addr, size_t size, int mode) { @@ -37,6 +43,37 @@ static inline int probe_mem_range(void *addr, size_t size, int mode) return 0; }
+static void purecap_sigsegv_handler(int n, siginfo_t *si, void *data) +{ + struct ucontext *uc = (struct ucontext *)data; + + ASSERT_EQ(si->si_signo, n); + TH_LOG("Signal (%d) occurred", n); + uc->uc_mcontext.pc += 4; + signal_status = 1; +} + +static void raise_sigsegv(void) +{ + void *addr; + long value; + int retval; + unsigned int *invalid_addr; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + invalid_addr = (unsigned int *)addr; + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + invalid_addr = (unsigned int *)(addr + MMAP_SIZE); + asm volatile("ldr %w0, [%1]" : "=r" (value) : "C" (invalid_addr) : "memory"); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); +} + /* Simple test to check our ability to create a new anonymous mapping * in the virtual address space of the calling process */ @@ -324,6 +361,79 @@ static inline void purecap_range_check(void) ASSERT_EQ(retval, 0); }
+/* test to verify mmap() behaviour when capability bounds are modified */ +static inline void purecap_mmap_bounds_check(void) +{ + int retval; + ptraddr_t address; + size_t res_length; + void *addr, *addr_cap, *ddc_cap, *ddc_value, *new_addr, *null_cap_addr; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + struct sigaction sa; + + /* generate a null-derived capability to be used later */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + + address = cheri_address_get(addr); + null_cap_addr = (void *)(uintptr_t)address; + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + + /* bounds of capability is increased by using root capability of the DDC + * register */ + ddc_value = cheri_ddc_get(); + ddc_cap = cheri_address_set(ddc_value, cheri_address_get(addr)); + ddc_cap = cheri_bounds_set(ddc_value, 2 * MMAP_SIZE); + ddc_cap = cheri_perms_and(ddc_value, cheri_perms_get(addr)); + EXPECT_EQ(0, probe_mem_range(addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + retval = munmap(ddc_cap, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* bounds of capability returned by mmap() is reduced, followed by a + * representable length check */ + addr = mmap(NULL, MMAP_SIZE, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + addr_cap = cheri_bounds_set(addr, MMAP_SIZE_REDUCED); + res_length = cheri_representable_length((unsigned long)addr_cap); + EXPECT_GE(res_length, MMAP_SIZE_REDUCED); + + retval = munmap(addr_cap, MMAP_SIZE); + ASSERT_EQ(retval, -EINVAL); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* overlapping mappings produce a reservation error */ + addr = mmap(null_cap_addr, MMAP_SIZE, prot, flags | MAP_FIXED, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + new_addr = mmap(null_cap_addr + MMAP_SIZE_REDUCED, MMAP_SIZE_REDUCED, + prot, flags | MAP_FIXED, -1, 0); + ASSERT_EQ((unsigned long)new_addr, (unsigned long)-ERESERVATION); + + retval = munmap(addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* register a signal handler to catch invalid memory access */ + sigemptyset(&sa.sa_mask); + sa.sa_handler = (sighandler_t)(void *)purecap_sigsegv_handler; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, NULL); + signal_status = 0; + raise_sigsegv(); + ASSERT_TRUE(signal_status); + TH_LOG("Segmenation fault caused by invalid memory access has been handled."); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -349,6 +459,11 @@ TEST(test_purecap_range_check) purecap_range_check(); }
+TEST(test_purecap_mmap_bounds_check) +{ + purecap_mmap_bounds_check(); +} + int main(void) { test_syscall_mmap(); @@ -356,5 +471,6 @@ int main(void) test_purecap_map_growsdown(); test_purecap_param_check(); test_purecap_range_check(); + test_purecap_mmap_bounds_check(); return 0; }
Attempting to remap a range larger than what is owned by the capability trigger an -EINVAL error. Additionally, mappings that have to be moved in order to satisfy the new constraints, expect the MREMAP_MAYMOVE flag to be specified. Failure to do so also triggers the -ERESERVATION error. Tests to verify this behaviour has been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index c7f1ddb395c5..aa552c8affba 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -434,6 +434,44 @@ static inline void purecap_mmap_bounds_check(void) TH_LOG("Segmenation fault caused by invalid memory access has been handled."); }
+/* test to verify mremap() behaviour when capability bounds are modified */ +static inline void purecap_mremap_bounds_check(void) +{ + void *addr, *new_addr; + int retval; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + /* moving a mapping with MREMAP_MAYMOVE flag specified */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + new_addr = mremap(addr, MMAP_SIZE_REDUCED, MMAP_SIZE, MREMAP_MAYMOVE, 0); + ASSERT_GT((unsigned long)new_addr, 0); + EXPECT_EQ(0, probe_mem_range(new_addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(new_addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* moving a mapping without MREMAP_MAYMOVE flag triggers a reservation error */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + new_addr = mremap(addr, MMAP_SIZE_REDUCED, MMAP_SIZE, 0, 0); + ASSERT_EQ((unsigned long)new_addr, (unsigned long)-ERESERVATION); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); + + /* attempt to resize a mapping range greater than what the capability owns */ + addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)addr, 0); + new_addr = mremap(addr, MMAP_SIZE, MMAP_SIZE, MREMAP_MAYMOVE, 0); + ASSERT_EQ((unsigned long)new_addr, (unsigned long)-EINVAL); + + retval = munmap(addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -464,6 +502,11 @@ TEST(test_purecap_mmap_bounds_check) purecap_mmap_bounds_check(); }
+TEST(test_purecap_mremap_bounds_check) +{ + purecap_mremap_bounds_check(); +} + int main(void) { test_syscall_mmap(); @@ -472,5 +515,6 @@ int main(void) test_purecap_param_check(); test_purecap_range_check(); test_purecap_mmap_bounds_check(); + test_purecap_mremap_bounds_check(); return 0; }
As mremap() doesn't take prot flags into consideration, the permissions of the original address are retained. If the permissions of the new address do not match the set of permissions of the old address, the syscall fails with a -EINVAL error code. A test to verify this behaviour has been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index aa552c8affba..889c5523303e 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -165,6 +165,68 @@ static void purecap_map_growsdown(void) ASSERT_EQ((unsigned long)addr, (unsigned long)-EOPNOTSUPP); }
+/* test to verify mremap() behaviour when permissions are modified */ +static void purecap_mremap_perms_check(void) +{ + void *old_addr, *new_addr, *ret; + int retval; + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + size_t old_perms, new_perms; + + /* permissions of capability returned by mremap must match the + * permissions returned by the original mapping */ + old_addr = mmap(NULL, MMAP_SIZE_REDUCED, prot, flags, -1, 0); + ASSERT_GT((unsigned long)old_addr, 0); + old_perms = cheri_perms_get(old_addr); + new_addr = mremap(old_addr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE, 0); + ASSERT_GT((unsigned long)new_addr, 0); + new_perms = cheri_perms_get(new_addr); + ASSERT_EQ(old_perms, new_perms); + EXPECT_EQ(0, probe_mem_range(new_addr, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(new_addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* remapping to a new_addr having reduced permissions from old_addr */ + old_addr = mmap(NULL, MMAP_SIZE_REDUCED, PROT_MAX(prot | PROT_EXEC) | + prot | PROT_EXEC, flags, -1, 0); + ASSERT_GT((unsigned long)old_addr, 0); + + new_addr = mmap(NULL, MMAP_SIZE, PROT_MAX(prot) | prot, flags, -1, 0); + ASSERT_GT((unsigned long)new_addr, 0); + retval = munmap(new_addr, MMAP_SIZE); + ASSERT_EQ(0, retval); + + ret = mremap(old_addr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE | MREMAP_FIXED, new_addr); + + EXPECT_EQ(0, probe_mem_range(ret, MMAP_SIZE, + PROBE_MODE_TOUCH | PROBE_MODE_VERIFY)); + + retval = munmap(ret, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + /* remapping to new_addr having increased permissions from old_addr */ + old_addr = mmap(NULL, MMAP_SIZE_REDUCED, PROT_MAX(prot) | prot, flags, + -1, 0); + ASSERT_GT((unsigned long)old_addr, 0); + + new_addr = mmap(NULL, MMAP_SIZE, PROT_MAX(prot | PROT_EXEC) | prot | + PROT_EXEC, flags, -1, 0); + ASSERT_GT((unsigned long)new_addr, 0); + retval = munmap(new_addr, MMAP_SIZE); + ASSERT_EQ(retval, 0); + + ret = mremap(old_addr, MMAP_SIZE_REDUCED, MMAP_SIZE, + MREMAP_MAYMOVE | MREMAP_FIXED, new_addr); + ASSERT_EQ((unsigned long)ret, (unsigned long)-EINVAL); + + retval = munmap(old_addr, MMAP_SIZE_REDUCED); + ASSERT_EQ(retval, 0); +}
/* test to validate parameters passed to address space management syscalls */ static inline void purecap_param_check(void) @@ -507,6 +569,11 @@ TEST(test_purecap_mremap_bounds_check) purecap_mremap_bounds_check(); }
+TEST(test_purecap_mremap_perms_check) +{ + purecap_mremap_perms_check(); +} + int main(void) { test_syscall_mmap(); @@ -516,5 +583,7 @@ int main(void) test_purecap_range_check(); test_purecap_mmap_bounds_check(); test_purecap_mremap_bounds_check(); + test_purecap_mremap_perms_check(); return 0; } +
As the mechanism of brk() depends on implicit address space reservation by moving the program break, it is unfavourable to the capability model. Hence an assumption is made that brk() is unnecessary and allocators making use of it can use mmap() instead. If used, it returns -ENOSYS. A test to verify this behaviour has been added.
Signed-off-by: Chaitanya S Prakash chaitanyas.prakash@arm.com --- tools/testing/selftests/arm64/morello/mmap.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/tools/testing/selftests/arm64/morello/mmap.c b/tools/testing/selftests/arm64/morello/mmap.c index 889c5523303e..db4d1fd98df9 100644 --- a/tools/testing/selftests/arm64/morello/mmap.c +++ b/tools/testing/selftests/arm64/morello/mmap.c @@ -534,6 +534,15 @@ static inline void purecap_mremap_bounds_check(void) ASSERT_EQ(retval, 0); }
+/* test to verify that using brk() results syscall failure */ +static inline void purecap_brk_check(void) +{ + int retval; + + retval = brk(NULL); + ASSERT_EQ(retval, -ENOSYS); +} + TEST(test_syscall_mmap) { syscall_mmap(); @@ -574,6 +583,11 @@ TEST(test_purecap_mremap_perms_check) purecap_mremap_perms_check(); }
+TEST(test_purecap_brk_check) +{ + purecap_brk_check(); +} + int main(void) { test_syscall_mmap(); @@ -584,6 +598,7 @@ int main(void) test_purecap_mmap_bounds_check(); test_purecap_mremap_bounds_check(); test_purecap_mremap_perms_check(); + test_purecap_brk_check(); return 0; }
linux-morello@op-lists.linaro.org