diff --git a/arch/x86/core/userspace.S b/arch/x86/core/userspace.S index ee39507319c..f06d33654be 100644 --- a/arch/x86/core/userspace.S +++ b/arch/x86/core/userspace.S @@ -27,7 +27,7 @@ SECTION_FUNC(TEXT, _x86_syscall_entry_stub) /* call_id is in ESI. bounds-check it, must be less than * K_SYSCALL_LIMIT */ - cmp $K_SYSCALL_LIMIT, %esi + cmp $_SYSCALL_LIMIT, %esi jae _bad_syscall _id_ok: @@ -79,7 +79,7 @@ _bad_syscall: * anyway, it's going to generate a kernel oops */ mov %esi, %eax - mov $K_SYSCALL_BAD, %esi + mov $_SYSCALL_BAD, %esi jmp _id_ok diff --git a/include/kernel.h b/include/kernel.h index 0a2351fe9b1..e3c55b10d70 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -3893,52 +3894,6 @@ extern void _sys_power_save_idle_exit(s32_t ticks); #include -#ifdef CONFIG_USERSPACE -/* Architecture-specific inline functions that may be indirectly called by - * application code due to their appearance in macros or other inline functions. - * - * Each arch should implement these in - */ - -/* Indicate whether we are currently running in user mode - * - * @return nonzero if the CPU is currently running with user permissions - */ -static inline int _arch_is_user_context(void); - -/** - * Indicate whether the CPU is currently in user mode - * - * @return nonzero if the CPU is currently running with user permissions - */ -static inline int _is_user_context(void) -{ - return _arch_is_user_context(); -} - -/* Interfaces for invoking system calls */ -static inline u32_t _arch_syscall_invoke6(u32_t arg1, u32_t arg2, u32_t arg3, - u32_t arg4, u32_t arg5, u32_t arg6, - u32_t call_id); - -static inline u32_t _arch_syscall_invoke5(u32_t arg1, u32_t arg2, u32_t arg3, - u32_t arg4, u32_t arg5, - u32_t call_id); - -static inline u32_t _arch_syscall_invoke4(u32_t arg1, u32_t arg2, u32_t arg3, - u32_t arg4, u32_t call_id); - -static inline u32_t _arch_syscall_invoke3(u32_t arg1, u32_t arg2, u32_t arg3, - u32_t call_id); - -static inline u32_t _arch_syscall_invoke2(u32_t arg1, u32_t arg2, - u32_t call_id); - -static inline u32_t _arch_syscall_invoke1(u32_t arg1, u32_t call_id); - -static inline u32_t _arch_syscall_invoke0(u32_t call_id); -#endif - #ifdef _ARCH_EXCEPT /* This archtecture has direct support for triggering a CPU exception */ #define _k_except_reason(reason) _ARCH_EXCEPT(reason) diff --git a/include/syscall.h b/include/syscall.h index fa23e1844e1..04405cffe19 100644 --- a/include/syscall.h +++ b/include/syscall.h @@ -8,15 +8,64 @@ #ifndef _ZEPHYR_SYSCALL_H_ #define _ZEPHYR_SYSCALL_H_ -/* Fixed system call IDs. We use #defines instead of enumeration so that if - * system calls are retired it does not shift the IDs of other system calls. - */ -#define K_SYSCALL_BAD 0 - -#define K_SYSCALL_LIMIT 1 - #ifndef _ASMLANGUAGE -#include +#include +#include +#include + +/* + * System Call Declaration macros + * + * These macros are used in public header files to declare system calls. + * They generate inline functions which have different implementations + * depending on the current compilation context: + * + * - Kernel-only code, or CONFIG_USERSPACE disabled, these inlines will + * directly call the implementation + * - User-only code, these inlines will marshal parameters and elevate + * privileges + * - Mixed or indeterminate code, these inlines will do a runtime check + * to determine what course of action is needed. + * + * All system calls require a handler function and an implementation function. + * These must follow a naming convention. For a system call named k_foo(): + * + * - The handler function will be named _handler_k_foo(). Handler functions + * are always of type _k_syscall_handler_t, verify arguments passed up + * from userspace, and call the implementation function. See + * documentation for that typedef for more information. + * - The implementation function will be named _impl_k_foo(). This is the + * actual implementation of the system call. + * + * The basic declartion macros are as follows. System calls with 0 to 10 + * parameters are supported. For a system call with N parameters, that returns + * a value and is* not implemented inline, the macro is as follows (N noted + * as {N} for clarity): + * + * K_SYSCALL_DECLARE{N}(id, name, ret, t0, p0, ... , t{N-1}, p{N-1}) + + * @param id System call ID, one of K_SYSCALL_* defines + * @param name Symbol name of the system call used to invoke it + * @param ret Data type of return value + * @param tX Data type of parameter X + * @param pX Name of parameter x + * + * For system calls that return no value: + * + * K_SYSCALL_DECLARE{n}_VOID(id, name, t0, p0, .... , t{N-1}, p{N-1}) + * + * This is identical to above except there is no 'ret' parameter. + * + * For system calls where the implementation is an inline function, we have + * + * K_SYSCALL_DECLARE{n}_INLINE(id, name, ret, t0, p0, ... , t{N-1}, p{N-1}) + * K_SYSCALL_DECLARE{n}_VOID_INLINE(id, name, t0, p0, ... , t{N-1}, p{N-1}) + * + * These are used in the same way as their non-INLINE counterparts. + * + * These macros are generated by scripts/gen_syscall_header.py and can be + * found in $OUTDIR/include/generated/syscall_macros.h + */ /** * @typedef _k_syscall_handler_t @@ -57,59 +106,141 @@ typedef u32_t (*_k_syscall_handler_t)(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, u32_t arg6, void *ssf); - - -extern const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT]; - +#ifdef CONFIG_USERSPACE /** - * @brief Runtime expression check for system call arguments + * Indicate whether we are currently running in user mode * - * Used in handler functions to perform various runtime checks on arguments, - * and generate a kernel oops if anything is not expected - * - * @param expr Boolean expression to verify, a false result will trigger an - * oops - * @param ssf Syscall stack frame argument passed to the handler function + * @return nonzero if the CPU is currently running with user permissions */ -#define _SYSCALL_VERIFY(expr, ssf) \ - do { \ - if (!(expr)) { \ - printk("FATAL: syscall failed check: " #expr "\n"); \ - _arch_syscall_oops(ssf); \ - } \ - } while (0) +static inline int _arch_is_user_context(void); /** - * @brief Runtime check that a pointer is a kernel object of expected type + * Indicate whether the CPU is currently in user mode * - * Passes along arguments to _k_object_validate() and triggers a kernel oops - * if the object wasn't valid or had incorrect permissions. - * - * @param ptr Untrusted kernel object pointer - * @param type Expected kernel object type - * @param init Whether this is an init function handler - * @param ssf Syscall stack frame argument passed to the handler function + * @return nonzero if the CPU is currently running with user permissions */ -#define _SYSCALL_IS_OBJ(ptr, type, init, ssf) \ - _SYSCALL_VERIFY(!_k_object_validate((void *)ptr, type, init), ssf) +static inline int _is_user_context(void) +{ + return _arch_is_user_context(); +} -/* Convenience macros for handler implementations */ -#define _SYSCALL_ARG0 ARG_UNUSED(arg1); ARG_UNUSED(arg2); ARG_UNUSED(arg3); \ - ARG_UNUSED(arg4); ARH_UNUSED(arg5); ARG_UNUSED(arg6) +/* + * Helper data structures for system calls with large argument lists + */ -#define _SYSCALL_ARG1 ARG_UNUSED(arg2); ARG_UNUSED(arg3); ARG_UNUSED(arg4); \ - ARG_UNUSED(arg5); ARG_UNUSED(arg6) +struct _syscall_7_args { + u32_t arg6; + u32_t arg7; +}; -#define _SYSCALL_ARG2 ARG_UNUSED(arg3); ARG_UNUSED(arg4); ARG_UNUSED(arg5); \ - ARG_UNUSED(arg6) +struct _syscall_8_args { + u32_t arg6; + u32_t arg7; + u32_t arg8; +}; -#define _SYSCALL_ARG3 ARG_UNUSED(arg4); ARG_UNUSED(arg5); ARG_UNUSED(arg6) +struct _syscall_9_args { + u32_t arg6; + u32_t arg7; + u32_t arg8; + u32_t arg9; +}; +struct _syscall_10_args { + u32_t arg6; + u32_t arg7; + u32_t arg8; + u32_t arg9; + u32_t arg10; +}; -#define _SYSCALL_ARG4 ARG_UNUSED(arg5); ARG_UNUSED(arg6) +/* + * Interfaces for invoking system calls + */ -#define _SYSCALL_ARG5 ARG_UNUSED(arg6) +static inline u32_t _arch_syscall_invoke0(u32_t call_id); + +static inline u32_t _arch_syscall_invoke1(u32_t arg1, u32_t call_id); + +static inline u32_t _arch_syscall_invoke2(u32_t arg1, u32_t arg2, + u32_t call_id); + +static inline u32_t _arch_syscall_invoke3(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t call_id); + +static inline u32_t _arch_syscall_invoke4(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t call_id); + +static inline u32_t _arch_syscall_invoke5(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, + u32_t call_id); + +static inline u32_t _arch_syscall_invoke6(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, u32_t arg6, + u32_t call_id); + +static inline u32_t _syscall_invoke7(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, u32_t arg6, + u32_t arg7, u32_t call_id) { + struct _syscall_7_args args = { + .arg6 = arg6, + .arg7 = arg7, + }; + + return _arch_syscall_invoke6(arg1, arg2, arg3, arg4, arg5, (u32_t)&args, + call_id); +} + +static inline u32_t _syscall_invoke8(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, u32_t arg6, + u32_t arg7, u32_t arg8, u32_t call_id) +{ + struct _syscall_8_args args = { + .arg6 = arg6, + .arg7 = arg7, + .arg8 = arg8, + }; + + return _arch_syscall_invoke6(arg1, arg2, arg3, arg4, arg5, (u32_t)&args, + call_id); +} + +static inline u32_t _syscall_invoke9(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, u32_t arg6, + u32_t arg7, u32_t arg8, u32_t arg9, + u32_t call_id) +{ + struct _syscall_9_args args = { + .arg6 = arg6, + .arg7 = arg7, + .arg8 = arg8, + .arg9 = arg9, + }; + + return _arch_syscall_invoke6(arg1, arg2, arg3, arg4, arg5, (u32_t)&args, + call_id); +} + +static inline u32_t _syscall_invoke10(u32_t arg1, u32_t arg2, u32_t arg3, + u32_t arg4, u32_t arg5, u32_t arg6, + u32_t arg7, u32_t arg8, u32_t arg9, + u32_t arg10, u32_t call_id) +{ + struct _syscall_10_args args = { + .arg6 = arg6, + .arg7 = arg7, + .arg8 = arg8, + .arg9 = arg9, + .arg10 = arg10 + }; + + return _arch_syscall_invoke6(arg1, arg2, arg3, arg4, arg5, (u32_t)&args, + call_id); +} + +#endif /* CONFIG_USERSPACE */ #endif /* _ASMLANGUAGE */ -#endif /* _ZEPHYR_SYSCALL_H_ */ +#endif + diff --git a/include/syscall_list.h b/include/syscall_list.h new file mode 100644 index 00000000000..5ce17b1fd39 --- /dev/null +++ b/include/syscall_list.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * SPDX-License-Identifier: Apache 2.0 + */ + + +#ifndef _ZEPHYR_SYSCALL_LIST_H_ +#define _ZEPHYR_SYSCALL_LIST_H_ + +#ifndef _ASMLANGUAGE + +enum { + K_SYSCALL_BAD, + + K_SYSCALL_LIMIT /* Always last */ +}; + +#endif /* _ASMLANGUAGE */ + +#endif /* _ZEPHYR_SYSCALL_LIST_H_ */ diff --git a/kernel/include/kernel_offsets.h b/kernel/include/kernel_offsets.h index 2fe0205534e..cf967dc11f3 100644 --- a/kernel/include/kernel_offsets.h +++ b/kernel/include/kernel_offsets.h @@ -70,4 +70,8 @@ GEN_ABSOLUTE_SYM(K_THREAD_SIZEOF, sizeof(struct k_thread)); /* size of the device structure. Used by linker scripts */ GEN_ABSOLUTE_SYM(_DEVICE_STRUCT_SIZE, sizeof(struct device)); +/* Access to enum values in asm code */ +GEN_ABSOLUTE_SYM(_SYSCALL_LIMIT, K_SYSCALL_LIMIT); +GEN_ABSOLUTE_SYM(_SYSCALL_BAD, K_SYSCALL_BAD); + #endif /* _kernel_offsets__h_ */ diff --git a/kernel/include/syscall_handler.h b/kernel/include/syscall_handler.h new file mode 100644 index 00000000000..f7720fa0f60 --- /dev/null +++ b/kernel/include/syscall_handler.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, Intel Corporation + * + * SPDX-License-Identifier: Apache 2.0 + */ + + +#ifndef _ZEPHYR_SYSCALL_HANDLER_H_ +#define _ZEPHYR_SYSCALL_HANDLER_H_ + +#ifdef CONFIG_USERSPACE + +#ifndef _ASMLANGUAGE +#include +#include +#include + +extern const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT]; + +/** + * @brief Runtime expression check for system call arguments + * + * Used in handler functions to perform various runtime checks on arguments, + * and generate a kernel oops if anything is not expected + * + * @param expr Boolean expression to verify, a false result will trigger an + * oops + * @param ssf Syscall stack frame argument passed to the handler function + */ +#define _SYSCALL_VERIFY(expr, ssf) \ + do { \ + if (!(expr)) { \ + printk("FATAL: syscall failed check: " #expr "\n"); \ + _arch_syscall_oops(ssf); \ + } \ + } while (0) + +/** + * @brief Runtime check that a user thread has proper access to a memory area + * + * Checks that the particular memory area is readable or writable by the + * currently running thread if the CPU was in user mode, and generates a kernel + * oops if it wasn't. Prevents userspace from getting the kernel to read or + * modify memory the thread does not have access to, or passing in garbage + * pointers that would crash/pagefault the kernel if accessed. + * + * @param ptr Memory area to examine + * @param size Size of the memory area + * @param write If the thread should be able to write to this memory, not just + * read it + * @param ssf Syscall stack frame argument passed to the handler function + */ +#define _SYSCALL_MEMORY(ptr, size, write, ssf) \ + _SYSCALL_VERIFY(!_arch_buffer_validate(ptr, size, write), ssf) + +/** + * @brief Runtime check that a pointer is a kernel object of expected type + * + * Passes along arguments to _k_object_validate() and triggers a kernel oops + * if the object wasn't valid or had incorrect permissions. + * + * @param ptr Untrusted kernel object pointer + * @param type Expected kernel object type + * @param init Whether this is an init function handler + * @param ssf Syscall stack frame argument passed to the handler function + */ +#define _SYSCALL_IS_OBJ(ptr, type, init, ssf) \ + _SYSCALL_VERIFY(!_k_object_validate((void *)ptr, type, init), ssf) + +/* Convenience macros for handler implementations */ +#define _SYSCALL_ARG0 ARG_UNUSED(arg1); ARG_UNUSED(arg2); ARG_UNUSED(arg3); \ + ARG_UNUSED(arg4); ARH_UNUSED(arg5); ARG_UNUSED(arg6) + +#define _SYSCALL_ARG1 ARG_UNUSED(arg2); ARG_UNUSED(arg3); ARG_UNUSED(arg4); \ + ARG_UNUSED(arg5); ARG_UNUSED(arg6) + +#define _SYSCALL_ARG2 ARG_UNUSED(arg3); ARG_UNUSED(arg4); ARG_UNUSED(arg5); \ + ARG_UNUSED(arg6) + +#define _SYSCALL_ARG3 ARG_UNUSED(arg4); ARG_UNUSED(arg5); ARG_UNUSED(arg6) + + +#define _SYSCALL_ARG4 ARG_UNUSED(arg5); ARG_UNUSED(arg6) + +#define _SYSCALL_ARG5 ARG_UNUSED(arg6) +#endif /* _ASMLANGUAGE */ + +#endif /* CONFIG_USERSPACE */ + +#endif /* _ZEPHYR_SYSCALL_H_ */ diff --git a/kernel/userspace.c b/kernel/userspace.c index cd5707b9a57..f540fd924a5 100644 --- a/kernel/userspace.c +++ b/kernel/userspace.c @@ -182,7 +182,7 @@ void _k_object_init(void *object) ko->flags |= K_OBJ_FLAG_INITIALIZED; } -static u32_t _syscall_bad_handler(u32_t bad_id, u32_t arg2, u32_t arg3, +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) { printk("Bad system call id %u invoked\n", bad_id); @@ -190,7 +190,6 @@ static u32_t _syscall_bad_handler(u32_t bad_id, u32_t arg2, u32_t arg3, CODE_UNREACHABLE; } -/* This table will eventually be generated by a script, placeholder for now */ const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT] = { - [K_SYSCALL_BAD] = _syscall_bad_handler, + [K_SYSCALL_BAD] = _handler_bad_syscall, };