zephyr/arch/xtensa/core/syscall_helper.c

93 lines
2.7 KiB
C
Raw Normal View History

/*
* Copyright (c) 2022 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
xtensa: make arch_user_string_nlen actually work arch_user_string_nlen() did not exactly work correctly as any invalid pointers being passed are de-referenced naively, which results in DTLB misses (MMU) or access errors (MPU). However, arch_user_string_nlen() should always return to the caller with appropriate error code set, and should never result in thread termination. Since we are usually going through syscalls when arch_user_string_nlen() is called, for MMU, the DTLB miss goes through double exception. Since the pointer is invalid, there is a high chance there is not even a L2 page table associated with that bad address. So the DTLB miss cannot be handled and it just keeps looping in double exception until there is another exception type where we get to the C handler. However, the stack frame is no longer the frame associated with the call to arch_user_string_nlen(), and the call return address would be incorrect. Forcing this incorrect address as the next PC would result in some other exceptions, e.g. illegal instruction, which would go to the C handler again. This time it will go to the end of handler and would result in thread termination. For MPU systems, access errors would simply result in thread terminal in the C handler. Because of these reasons, change the arch_user_string_nlen() to check if the memory region can be accessed under kernel mode first before feeding it to strnlen(). Also remove the exception fixup arrays as there is nothing there anymore. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2024-04-10 02:29:29 +08:00
#include <string.h>
#include <zephyr/arch/xtensa/syscall.h>
xtensa: make arch_user_string_nlen actually work arch_user_string_nlen() did not exactly work correctly as any invalid pointers being passed are de-referenced naively, which results in DTLB misses (MMU) or access errors (MPU). However, arch_user_string_nlen() should always return to the caller with appropriate error code set, and should never result in thread termination. Since we are usually going through syscalls when arch_user_string_nlen() is called, for MMU, the DTLB miss goes through double exception. Since the pointer is invalid, there is a high chance there is not even a L2 page table associated with that bad address. So the DTLB miss cannot be handled and it just keeps looping in double exception until there is another exception type where we get to the C handler. However, the stack frame is no longer the frame associated with the call to arch_user_string_nlen(), and the call return address would be incorrect. Forcing this incorrect address as the next PC would result in some other exceptions, e.g. illegal instruction, which would go to the C handler again. This time it will go to the end of handler and would result in thread termination. For MPU systems, access errors would simply result in thread terminal in the C handler. Because of these reasons, change the arch_user_string_nlen() to check if the memory region can be accessed under kernel mode first before feeding it to strnlen(). Also remove the exception fixup arrays as there is nothing there anymore. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2024-04-10 02:29:29 +08:00
#include <zephyr/internal/syscall_handler.h>
#include <xtensa_internal.h>
#ifdef CONFIG_XTENSA_SYSCALL_USE_HELPER
uintptr_t xtensa_syscall_helper(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3, uintptr_t arg4,
uintptr_t arg5, uintptr_t arg6,
uintptr_t call_id)
{
register uintptr_t a2 __asm__("%a2") = call_id;
register uintptr_t a6 __asm__("%a6") = arg1;
register uintptr_t a3 __asm__("%a3") = arg2;
register uintptr_t a4 __asm__("%a4") = arg3;
register uintptr_t a5 __asm__("%a5") = arg4;
register uintptr_t a8 __asm__("%a8") = arg5;
register uintptr_t a9 __asm__("%a9") = arg6;
__asm__ volatile("syscall\n\t"
: "=r" (a2)
: "r" (a2), "r" (a6), "r" (a3), "r" (a4),
"r" (a5), "r" (a8), "r" (a9)
: "memory");
return a2;
}
#endif /* CONFIG_XTENSA_SYSCALL_USE_HELPER */
#if XCHAL_HAVE_THREADPTR == 0
#include <xtensa/config/core-isa.h>
#include <xtensa/config/core.h>
bool xtensa_is_user_context(void)
{
uint32_t ret;
__asm__ volatile(".global xtensa_is_user_context_epc\n"
" xtensa_is_user_context_epc:\n"
" syscall\n"
" mov %0, a2\n"
: "=r"(ret) : : "a2");
return ret != 0;
}
#endif /* XCHAL_HAVE_THREADPTR */
xtensa: make arch_user_string_nlen actually work arch_user_string_nlen() did not exactly work correctly as any invalid pointers being passed are de-referenced naively, which results in DTLB misses (MMU) or access errors (MPU). However, arch_user_string_nlen() should always return to the caller with appropriate error code set, and should never result in thread termination. Since we are usually going through syscalls when arch_user_string_nlen() is called, for MMU, the DTLB miss goes through double exception. Since the pointer is invalid, there is a high chance there is not even a L2 page table associated with that bad address. So the DTLB miss cannot be handled and it just keeps looping in double exception until there is another exception type where we get to the C handler. However, the stack frame is no longer the frame associated with the call to arch_user_string_nlen(), and the call return address would be incorrect. Forcing this incorrect address as the next PC would result in some other exceptions, e.g. illegal instruction, which would go to the C handler again. This time it will go to the end of handler and would result in thread termination. For MPU systems, access errors would simply result in thread terminal in the C handler. Because of these reasons, change the arch_user_string_nlen() to check if the memory region can be accessed under kernel mode first before feeding it to strnlen(). Also remove the exception fixup arrays as there is nothing there anymore. Signed-off-by: Daniel Leung <daniel.leung@intel.com>
2024-04-10 02:29:29 +08:00
size_t arch_user_string_nlen(const char *s, size_t maxsize, int *err_arg)
{
/* Check if we can actually read the whole length.
*
* arch_user_string_nlen() is supposed to naively go through
* the string passed from user thread, and relies on page faults
* to catch inaccessible strings, such that user thread can pass
* a string that is shorter than the max length this function
* caller expects. So at least we want to make sure kernel has
* access to the whole length, aka. memory being mapped.
* Note that arch_user_string_nlen() should never result in
* thread termination due to page faults, and must always
* return to the caller with err_arg set or cleared.
* For MMU systems, unmapped memory will result in a DTLB miss
* and that might trigger an infinite DTLB miss storm if
* the corresponding L2 page table never exists in the first
* place (which would result in DTLB misses through L1 page
* table), until some other exceptions occur to break
* the cycle.
* For MPU systems, this would simply results in access errors
* and the exception handler will terminate the thread.
*/
if (!xtensa_mem_kernel_has_access((void *)s, maxsize, 0)) {
/*
* API says we need to set err_arg to -1 if there are
* any errors.
*/
*err_arg = -1;
return 0;
}
/* No error and we can proceed to getting the string length. */
*err_arg = 0;
return strnlen(s, maxsize);
}