From c8188f6722d2114312b3954bf112a99b2d633e78 Mon Sep 17 00:00:00 2001 From: Andrew Boie Date: Fri, 22 Jun 2018 14:31:51 -0700 Subject: [PATCH] userspace: add functions for copying to/from user We now have functions for handling all the details of copying data to/from user mode, including C strings and copying data into resource pool allocations. Signed-off-by: Andrew Boie --- kernel/include/syscall_handler.h | 110 +++++++++++++++++++++++++++++ kernel/userspace.c | 117 +++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/kernel/include/syscall_handler.h b/kernel/include/syscall_handler.h index 6a25f6894d7..eb0c1f16848 100644 --- a/kernel/include/syscall_handler.h +++ b/kernel/include/syscall_handler.h @@ -125,6 +125,116 @@ extern void _thread_perms_all_clear(struct k_thread *thread); */ void _k_object_uninit(void *obj); +/** + * @brief Obtain the size of a C string passed from user mode + * + * Given a C string pointer and a maximum size, obtain the true + * size of the string (not including the trailing NULL byte) just as + * if calling strnlen() on it, with the same semantics of strnlen() with + * respect to the return value and the maxlen parameter. + * + * Any memory protection faults triggered by the examination of the string + * will be safely handled and an error code returned. + * + * NOTE: Doesn't guarantee that user mode has actual access to this + * string, you will need to still do a Z_SYSCALL_MEMORY_READ() + * with the obtained size value to guarantee this. + * + * @param src String to measure size of + * @param maxlen Maximum number of characters to examine + * @param err Pointer to int, filled in with -1 on memory error, 0 on + * success + * @return undefined on error, or strlen(src) if that is less than maxlen, or + * maxlen if there were no NULL terminating characters within the + * first maxlen bytes. + */ +static inline size_t z_user_string_nlen(const char *src, size_t maxlen, + int *err) +{ + return z_arch_user_string_nlen(src, maxlen, err); +} + +/** + * @brief Copy data from userspace into a resource pool allocation + * + * Given a pointer and a size, allocate a similarly sized buffer in the + * caller's resource pool and copy all the data within it to the newly + * allocated buffer. This will need to be freed later with k_free(). + * + * Checks are done to ensure that the current thread would have read + * access to the provided buffer. + * + * @param src Source memory address + * @param size Size of the memory buffer + * @return An allocated buffer with the data copied within it, or NULL + * if some error condition occurred + */ +extern void *z_user_alloc_from_copy(void *src, size_t size); + +/** + * @brief Copy data from user mode + * + * Given a userspace pointer and a size, copies data from it into a provided + * destination buffer, performing checks to ensure that the caller would have + * appropriate access when in user mode. + * + * @param dst Destination memory buffer + * @param src Source memory buffer, in userspace + * @param size Number of bytes to copy + * @retval 0 On success + * @retval EFAULT On memory access error + */ +extern int z_user_from_copy(void *dst, void *src, size_t size); + +/** + * @brief Copy data to user mode + * + * Given a userspace pointer and a size, copies data to it from a provided + * source buffer, performing checks to ensure that the caller would have + * appropriate access when in user mode. + * + * @param dst Destination memory buffer, in userspace + * @param src Source memory buffer + * @param size Number of bytes to copy + * @retval 0 On success + * @retval EFAULT On memory access error + */ +extern int z_user_to_copy(void *dst, void *src, size_t size); + +/** + * @brief Copy a C string from userspace into a resource pool allocation + * + * Given a C string and maximum length, duplicate the string using an + * allocation from the calling thread's resource pool. This will need to be + * freed later with k_free(). + * + * Checks are performed to ensure that the string is valid memory and that + * the caller has access to it in user mode. + * + * @param src Source string pointer, in userspace + * @param maxlen Maximum size of the string including trailing NULL + * @return The duplicated string, or NULL if an error occurred. + */ +extern char *z_user_string_alloc_copy(char *src, size_t maxlen); + +/** + * @brief Copy a C string from userspace into a provided buffer + * + * Given a C string and maximum length, copy the string into a buffer. + * + * Checks are performed to ensure that the string is valid memory and that + * the caller has access to it in user mode. + * + * @param dst Destination buffer + * @param src Source string pointer, in userspace + * @param maxlen Maximum size of the string including trailing NULL + * @retval 0 on success + * @retval EINVAL if the source string is too long with respect + * to maxlen + * @retval EFAULT On memory access error + */ +extern int z_user_string_copy(char *dst, char *src, size_t maxlen); + #define Z_OOPS(expr) \ do { \ if (expr) { \ diff --git a/kernel/userspace.c b/kernel/userspace.c index ab1cc4db951..19801cc6369 100644 --- a/kernel/userspace.c +++ b/kernel/userspace.c @@ -475,6 +475,123 @@ void _k_object_uninit(void *object) ko->flags &= ~K_OBJ_FLAG_INITIALIZED; } +/* + * Copy to/from helper functions used in syscall handlers + */ +void *z_user_alloc_from_copy(void *src, size_t size) +{ + void *dst = NULL; + int key; + + key = irq_lock(); + + /* Does the caller in user mode have access to read this memory? */ + if (Z_SYSCALL_MEMORY_READ(src, size)) { + goto out_err; + } + + dst = z_thread_malloc(size); + if (!dst) { + printk("out of thread resource pool memory (%zu)", size); + goto out_err; + } + + memcpy(dst, src, size); +out_err: + irq_unlock(key); + return dst; +} + +static int user_copy(void *dst, void *src, size_t size, bool to_user) +{ + int ret = EFAULT; + int key; + + key = irq_lock(); + + /* Does the caller in user mode have access to this memory? */ + if (to_user ? Z_SYSCALL_MEMORY_WRITE(dst, size) : + Z_SYSCALL_MEMORY_READ(src, size)) { + goto out_err; + } + + memcpy(dst, src, size); + ret = 0; +out_err: + irq_unlock(key); + return ret; +} + +int z_user_from_copy(void *dst, void *src, size_t size) +{ + return user_copy(dst, src, size, false); +} + +int z_user_to_copy(void *dst, void *src, size_t size) +{ + return user_copy(dst, src, size, true); +} + +char *z_user_string_alloc_copy(char *src, size_t maxlen) +{ + unsigned long actual_len; + int key, err; + char *ret = NULL; + + key = irq_lock(); + actual_len = z_user_string_nlen(src, maxlen, &err); + if (err) { + goto out; + } + if (actual_len == maxlen) { + /* Not NULL terminated */ + printk("string too long %p (%lu)\n", src, actual_len); + goto out; + } + if (__builtin_uaddl_overflow(actual_len, 1, &actual_len)) { + printk("overflow\n"); + goto out; + } + + ret = z_user_alloc_from_copy(src, actual_len); +out: + irq_unlock(key); + return ret; +} + +int z_user_string_copy(char *dst, char *src, size_t maxlen) +{ + unsigned long actual_len; + int key, ret, err; + + key = irq_lock(); + actual_len = z_user_string_nlen(src, maxlen, &err); + if (err) { + ret = EFAULT; + goto out; + } + if (actual_len == maxlen) { + /* Not NULL terminated */ + printk("string too long %p (%lu)\n", src, actual_len); + ret = EINVAL; + goto out; + } + if (__builtin_uaddl_overflow(actual_len, 1, &actual_len)) { + printk("overflow\n"); + ret = EINVAL; + goto out; + } + + ret = z_user_from_copy(dst, src, actual_len); +out: + irq_unlock(key); + return ret; +} + +/* + * Default handlers if otherwise unimplemented + */ + static u32_t handler_bad_syscall(u32_t bad_id, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, u32_t arg6, void *ssf) {