Add tests for capability enforcement via the standard copy routines (copy_{to, from}_user, {get, put}_user), futex operations and the explicit uaccess checks used in iov_iter via readv and writev.
Signed-off-by: Akram Ahmad Akram.Ahmad@arm.com ---
Hi everyone,
This patch extends kselftests with tests for various uaccess routines. Broadly speaking, this tests the standard uaccess routines, futex operations, and iov_iter (the latter is implemented by testing readv and writev syscalls).
The tests currently examine the effects of passing a capability with a cleared tag, invalid permissions, and an invalid base address offset. These are tested separately from one another, and I would especially appreciate any comments on how this could be improved if needed (particularly on the futex test).
Thank you and happy holidays!
Akram
[1] The branch for this patch: https://git.morello-project.org/arkamnite/linux/-/commits/morello%2Fuaccess
[2] The issue ticket for this patch: https://git.morello-project.org/morello/kernel/linux/-/issues/58
tools/testing/selftests/Makefile | 1 + .../testing/selftests/arm64/morello/Makefile | 2 +- .../testing/selftests/arm64/morello/uaccess.c | 491 ++++++++++++++++++ 3 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/arm64/morello/uaccess.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 90a62cf75008..82abe4ddbb29 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -86,6 +86,7 @@ TARGETS += timers endif TARGETS += tmpfs TARGETS += tpm2 +TARGETS += uaccess TARGETS += user TARGETS += vDSO TARGETS += mm diff --git a/tools/testing/selftests/arm64/morello/Makefile b/tools/testing/selftests/arm64/morello/Makefile index ecfa6fc5e989..7b794d3e436c 100644 --- a/tools/testing/selftests/arm64/morello/Makefile +++ b/tools/testing/selftests/arm64/morello/Makefile @@ -17,7 +17,7 @@ CFLAGS += $(CLANG_FLAGS) $(CFLAGS_PURECAP) $(CFLAGS_COMMON) LDFLAGS += $(CLANG_LDFLAGS) $(CLANG_FLAGS) -nostdlib -static
SRCS := $(wildcard *.c) $(wildcard *.S) -PROGS := bootstrap clone exit mmap read_write sched signal +PROGS := bootstrap clone exit mmap read_write sched signal uaccess DEPS := $(wildcard *.h)
# these are the final executables diff --git a/tools/testing/selftests/arm64/morello/uaccess.c b/tools/testing/selftests/arm64/morello/uaccess.c new file mode 100644 index 000000000000..66d93137b819 --- /dev/null +++ b/tools/testing/selftests/arm64/morello/uaccess.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2023 Arm Limited + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +#include <asm/fcntl.h> +#include <asm-generic/errno-base.h> +#include <cheriintrin.h> +#include <linux/fs.h> +#include <linux/futex.h> +#include <linux/mman.h> +#include <linux/sysinfo.h> +#include <linux/socket.h> +#include <linux/time.h> +#include <linux/uio.h> + +#include "freestanding.h" + +#define FILE_PERM 0666 +#define MSG_LEN 16 +#define NR_FUTEXES 2 + +static struct futex_waitv waitv[NR_FUTEXES]; +uint32_t futexes[NR_FUTEXES] = {1}; + +static const char file[] = "/my_file.txt"; +static int fd; +static int my_socket; + +static int open_file(void) +{ + return syscall(__NR_openat, 0, file, O_RDWR | O_CREAT, 0666); +} + +static int close_file(int __fd) +{ + return syscall(__NR_close, __fd); +} + +static __always_inline +void copy_to_user1(void) +{ + int result; + struct sysinfo my_sysinfo; + struct sysinfo *p_si = &my_sysinfo; + struct sysinfo my_sysinfo2; + struct sysinfo *p_si2 = &my_sysinfo2 + 10 * sizeof(struct sysinfo); + + result = syscall(__NR_sysinfo, p_si); + TH_LOG("%s: sysinfo with valid capability", __func__); + ASSERT_EQ(result, 0); + + TH_LOG("%s: sysinfo with invalid capability offset", __func__); + result = syscall(__NR_sysinfo, p_si2); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void copy_to_user2(void) +{ + int result; + struct sysinfo my_sysinfo; + struct sysinfo *cap = &my_sysinfo; + + TH_LOG("%s: sysinfo with cleared capability tag", __func__); + cap = (struct sysinfo *)cheri_tag_clear(cap); + result = syscall(__NR_sysinfo, cap); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void copy_to_user3(void) +{ + int result; + struct sysinfo my_sysinfo; + struct sysinfo *cap = &my_sysinfo; + + TH_LOG("%s: sysinfo with no capability permissions", __func__); + cap = (struct sysinfo *)cheri_perms_and(cap, 0); + result = syscall(__NR_sysinfo, cap); + ASSERT_EQ(result, -EFAULT); +} + +TEST(test_copy_to_user) +{ + copy_to_user1(); + copy_to_user2(); + copy_to_user3(); +} + +static __always_inline +void copy_from_user1(void) +{ + int result; + size_t my_offset = sizeof(int); + size_t *good_p = &my_offset; + size_t *bad_p = &my_offset + sizeof(int); + + /** + * Setup the parameters for our syscall: + * fd_in, fd_out: these can be the same fd. + * off_in: the capability pointing to a buffer for the offset + * off_out: NULL, as we do not need this. + * len: MSG_LEN + */ + fd = open_file(); + + result = syscall(__NR_copy_file_range, fd, good_p, fd, NULL, MSG_LEN, 0); + TH_LOG("%s: copy_file_range with valid capability", __func__); + ASSERT_GE(0, result); + + result = syscall(__NR_copy_file_range, fd, bad_p, fd, NULL, MSG_LEN, 0); + TH_LOG("%s: copy_file_range with invalid capability offset", __func__); + ASSERT_EQ(result, -EFAULT); + close_file(fd); +} + +static __always_inline +void copy_from_user2(void) +{ + int result; + size_t my_offset = sizeof(int); + size_t *good_p = &my_offset; + size_t *bad_p = (size_t *)cheri_tag_clear(good_p); + + fd = open_file(); + + result = syscall(__NR_copy_file_range, fd, bad_p, fd, NULL, MSG_LEN, 0); + TH_LOG("%s: copy_file_range with cleared capability tag", __func__); + ASSERT_EQ(result, -EFAULT); + close_file(fd); +} + +static __always_inline +void copy_from_user3(void) +{ + int result; + size_t my_offset = sizeof(int); + size_t *bad_p = (size_t *)cheri_perms_and(&my_offset, 0); + + fd = open_file(); + + result = syscall(__NR_copy_file_range, fd, bad_p, fd, NULL, MSG_LEN, 0); + TH_LOG("%s: copy_file_range with capability with no permissions", __func__); + ASSERT_EQ(result, -EFAULT); + close_file(fd); +} + +TEST(test_copy_from_user) +{ + copy_from_user1(); + copy_from_user2(); + copy_from_user3(); +} + +typedef unsigned short sa_family_t; + +struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; +}; + +static __always_inline +void get_user1(void) +{ + int result; + struct sockaddr sa; + struct sockaddr sa2; + struct sockaddr *my_sockaddr_ptr = &sa2 + sizeof(int) * 1000; + + // socket(AF_INET, SOCK_STREAM, 0) + my_socket = syscall(__NR_socket, 2, 1, 0); + TH_LOG("%s: socket syscall complete", __func__); + ASSERT_GE(my_socket, 0); + + int sa_len = sizeof(sa); + int *my_len_pointer = &sa_len; + + TH_LOG("%s: getsockname with valid capability", __func__); + result = syscall(__NR_getsockname, my_socket, &sa, &sa_len); + ASSERT_EQ(result, 0); + + sa_len = sizeof(sa2); + TH_LOG("%s: getsockname with invalid capability offset", __func__); + result = syscall(__NR_getsockname, my_socket, my_sockaddr_ptr, my_len_pointer); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void get_user2(void) +{ + int result; + struct sockaddr sa; + int sa_len = sizeof(sa); + struct sockaddr *p_sockaddr = cheri_tag_clear(&sa); + + // socket(AF_INET, SOCK_STREAM, 0) + my_socket = syscall(__NR_socket, 2, 1, 0); + ASSERT_GE(my_socket, 0); + + result = syscall(__NR_getsockname, my_socket, p_sockaddr, &sa_len); + TH_LOG("%s: getsockname with cleared capability tag", __func__); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void get_user3(void) +{ + int result; + struct sockaddr sa; + int sa_len = sizeof(sa); + struct sockaddr *p_sockaddr = cheri_perms_and(&sa, 0); + + // socket(AF_INET, SOCK_STREAM, 0) + my_socket = syscall(__NR_socket, 2, 1, 0); + ASSERT_GE(my_socket, 0); + + result = syscall(__NR_getsockname, my_socket, p_sockaddr, &sa_len); + TH_LOG("%s: getsockname with no capability permissions", __func__); + ASSERT_EQ(result, -EFAULT); +} + +TEST(test_get_user) +{ + get_user1(); + get_user2(); + get_user3(); +} + +static __always_inline +void put_user1(void) +{ + int result; + struct timeval tv; + struct timeval *p_tv = &tv; + + result = syscall(__NR_gettimeofday, p_tv, NULL); + TH_LOG("%s: gettimeofday with valid capability", __func__); + ASSERT_EQ(result, 0); + + p_tv = &tv + sizeof(struct timeval) * 1000; + result = syscall(__NR_gettimeofday, p_tv, NULL); + TH_LOG("%s: gettimeofday with invalid capability offset", __func__); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void put_user2(void) +{ + int result; + struct timeval tv; + struct timeval *p_tv = &tv; + + p_tv = cheri_tag_clear(p_tv); + result = syscall(__NR_gettimeofday, p_tv, NULL); + TH_LOG("%s: gettimeofday with cleared capability tag", __func__); + ASSERT_EQ(result, -EFAULT); +} + +static __always_inline +void put_user3(void) +{ + int result; + struct timeval tv; + struct timeval *p_tv = &tv; + + p_tv = cheri_perms_and(p_tv, 0); + result = syscall(__NR_gettimeofday, p_tv, NULL); + TH_LOG("%s: gettimeofday with capability with no permissions", __func__); + ASSERT_EQ(result, -EFAULT); +} + +TEST(test_put_user) +{ + put_user1(); + put_user2(); + put_user3(); +} + +static inline int futex_waitv(volatile struct futex_waitv *waiters, unsigned long nr_waiters, + unsigned long flags, struct timespec *timo, clockid_t clockid) +{ + return syscall(__NR_futex_waitv, waiters, nr_waiters, flags, timo, clockid); +} + +static __always_inline +void futex1(void) +{ + struct timespec to; + + for (int i = 0; i < NR_FUTEXES; i++) { + waitv[i].uaddr = (uintptr_t)cheri_tag_clear(&futexes[i]); + waitv[i].flags = FUTEX_32 | FUTEX_PRIVATE_FLAG; + waitv[i].val = i; + waitv[i].__reserved = 0; + // This is necessary otherwise the loop is optimised away + TH_LOG("%s: waitv[%d] initialised", __func__, i); + } + + int res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); + + ASSERT_EQ(res, -EFAULT); +} + +TEST(test_futex) +{ + futex1(); +} + +static __always_inline +void explicit_readv1(void) +{ + ssize_t bytes_read; + char buf0[2]; + char buf1[4]; + char buf2[6]; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = buf0; + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: readv with valid capability", __func__); + bytes_read = syscall(__NR_readv, fd, iov, iovcnt); + close_file(fd); + ASSERT_GE(bytes_read, 0); +} + +static __always_inline +void explicit_readv2(void) +{ + ssize_t bytes_read; + char buf0[2]; + char buf1[4]; + char buf2[6]; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = buf0; + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: readv with invalid capability tag", __func__); + bytes_read = syscall(__NR_readv, fd, cheri_tag_clear(iov), iovcnt); + close_file(fd); + ASSERT_EQ(bytes_read, -EFAULT); +} + +static __always_inline +void explicit_readv3(void) +{ + ssize_t bytes_read; + char buf0[2]; + char buf1[4]; + char buf2[6]; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = cheri_perms_and(buf0, 0); + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: readv with invalid capability permissions", __func__); + bytes_read = syscall(__NR_readv, fd, cheri_perms_and(iov, 0), iovcnt); + close_file(fd); + ASSERT_EQ(bytes_read, -EFAULT); +} + +static __always_inline +void explicit_writev1(void) +{ + ssize_t bytes_written; + char *buf0 = "Hello I am the first char buffer!\n"; + char *buf1 = "Hello, I am the second char buffer.\n"; + char *buf2 = "Hello, I am the third and final char buffer.\n"; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = buf0; + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: writev with valid capability", __func__); + bytes_written = syscall(__NR_writev, fd, iov, iovcnt); + close_file(fd); + ASSERT_GE(bytes_written, 0); +} + +static __always_inline +void explicit_writev2(void) +{ + ssize_t bytes_written; + char *buf0 = "Hello I am the first char buffer!\n"; + char *buf1 = "Hello, I am the second char buffer.\n"; + char *buf2 = "Hello, I am the third and final char buffer.\n"; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = buf0; + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: writev with invalid capability tag", __func__); + bytes_written = syscall(__NR_writev, fd, cheri_tag_clear(iov), iovcnt); + close_file(fd); + ASSERT_EQ(bytes_written, -EFAULT); +} + +static __always_inline +void explicit_writev3(void) +{ + ssize_t bytes_written; + char *buf0 = "Hello I am the first char buffer!\n"; + char *buf1 = "Hello, I am the second char buffer.\n"; + char *buf2 = "Hello, I am the third and final char buffer.\n"; + struct iovec iov[3]; + int iovcnt; + + iov[0].iov_base = cheri_perms_and(buf0, 0); + iov[0].iov_len = sizeof(buf0); + iov[1].iov_base = buf1; + iov[1].iov_len = sizeof(buf1); + iov[2].iov_base = buf2; + iov[2].iov_len = sizeof(buf2); + + iovcnt = sizeof(iov) / sizeof(struct iovec); + + fd = open_file(); + TH_LOG("%s: writev with invalid capability permissions", __func__); + bytes_written = syscall(__NR_writev, fd, cheri_perms_and(iov, 0), iovcnt); + close_file(fd); + ASSERT_EQ(bytes_written, -EFAULT); +} + +/* + * Test readv and writev where the array of iovecs is provided + * by a capability. + */ +TEST(test_explicit_iov_iter) +{ + explicit_readv1(); + explicit_readv2(); + explicit_readv3(); + explicit_writev1(); + explicit_writev2(); + explicit_writev3(); +} + +int main(void) +{ + test_copy_to_user(); + test_copy_from_user(); + test_get_user(); + test_put_user(); + test_futex(); + test_explicit_iov_iter(); + return 0; +}