syscalls: reorganize headers

- syscall.h now contains those APIs needed to support invoking calls
  from user code. Some stuff moved out of main kernel.h.
- syscall_handler.h now contains directives useful for implementing
  system call handler functions. This header is not pulled in by
  kernel.h and is intended to be used by C files implementing kernel
  system calls and driver subsystem APIs.
- syscall_list.h now contains the #defines for system call IDs. This
  list is expected to grow quite large so it is put in its own header.
  This is now an enumerated type instead of defines to make things
  easier as we introduce system calls over the new few months. In the
  fullness of time when we desire to have a fixed userspace/kernel ABI,
  this can always be converted to defines.

Some new code added:

- _SYSCALL_MEMORY() macro added to check memory regions passed up from
  userspace in handler functions
- _syscall_invoke{7...10}() inline functions declare for invoking system
  calls with more than 6 arguments. 10 was chosen as the limit as that
  corresponds to the largest arg list we currently have
  which is for k_thread_create()

Other changes

- auto-generated K_SYSCALL_DECLARE* macros documented
- _k_syscall_table in userspace.c is not a placeholder. There's no
  strong need to generate it and doing so would require the introduction
  of a third build phase.

Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This commit is contained in:
Andrew Boie 2017-09-23 12:05:49 -07:00 committed by Andrew Boie
commit 13ca6fe284
7 changed files with 298 additions and 98 deletions

View file

@ -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

View file

@ -28,6 +28,7 @@
#include <kernel_version.h>
#include <drivers/rand32.h>
#include <kernel_arch_thread.h>
#include <syscall.h>
#ifdef __cplusplus
extern "C" {
@ -3893,52 +3894,6 @@ extern void _sys_power_save_idle_exit(s32_t ticks);
#include <arch/cpu.h>
#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 <arch/cpu.h>
*/
/* 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)

View file

@ -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 <misc/printk.h>
#include <zephyr/types.h>
#include <syscall_list.h>
#include <syscall_macros.h>
/*
* 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

21
include/syscall_list.h Normal file
View file

@ -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_ */

View file

@ -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_ */

View file

@ -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 <kernel.h>
#include <misc/printk.h>
#include <nano_internal.h>
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_ */

View file

@ -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,
};