Skip to content

Commit

Permalink
userspace: Support for split 64 bit arguments
Browse files Browse the repository at this point in the history
System call arguments, at the arch layer, are single words.  So
passing wider values requires splitting them into two registers at
call time.  This gets even more complicated for values (e.g
k_timeout_t) that may have different sizes depending on configuration.
This patch adds a feature to gen_syscalls.py to detect functions with
wide arguments and automatically generates code to split/unsplit them.

Unfortunately the current scheme of Z_SYSCALL_DECLARE_* macros won't
work with functions like this, because for N arguments (our current
maximum N is 10) there are 2^N possible configurations of argument
widths.  So this generates the complete functions for each handler and
wrapper, effectively doing in python what was originally done in the
preprocessor.

Another complexity is that traditional the z_hdlr_*() function for a
system call has taken the raw list of word arguments, which does not
work when some of those arguments must be 64 bit types.  So instead of
using a single Z_SYSCALL_HANDLER macro, this splits the job of
z_hdlr_*() into two steps: An automatically-generated unmarshalling
function, z_mrsh_*(), which then calls a user-supplied verification
function z_vrfy_*().  The verification function is typesafe, and is a
simple C function with exactly the same argument and return signature
as the syscall impl function.  It is also not responsible for
validating the pointers to the extra parameter array or a wide return
value, that code gets automatically generated.

This commit includes new vrfy/msrh handling for all syscalls invoked
during CI runs.  Future commits will port the less testable code.

Signed-off-by: Andy Ross <[email protected]>
  • Loading branch information
Andy Ross authored and nashif committed Sep 12, 2019
1 parent 1846fc6 commit 6564974
Show file tree
Hide file tree
Showing 36 changed files with 739 additions and 972 deletions.
257 changes: 47 additions & 210 deletions doc/reference/usermode/syscalls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ All system calls have the following components:
system call. The implementation function may assume that all parameters
passed in have been validated if it was invoked from user mode.

* A **handler function**, which wraps the implementation function and does
validation of all the arguments passed in.
* A **verification function**, which wraps the implementation function
and does validation of all the arguments passed in.

* An **unmarshalling function**, which is an automatically generated
handler that must be included by user source code.

C Prototype
***********
Expand Down Expand Up @@ -122,9 +125,6 @@ the project out directory under ``include/generated/``:
which is expressed in ``include/generated/syscall_list.h``. It is the name
of the API in uppercase, prefixed with ``K_SYSCALL_``.

* A prototype for the handler function is also created in
``include/generated/syscall_list.h``

* An entry for the system call is created in the dispatch table
``_k_sycall_table``, expressed in ``include/generated/syscall_dispatch.c``

Expand All @@ -135,6 +135,8 @@ the project out directory under ``include/generated/``:
API call, but the sensor subsystem is not enabled, the weak handler
will be invoked instead.

* An unmarshalling function is defined in ``include/generated/<name>_mrsh.c``

The body of the API is created in the generated system header. Using the
example of :c:func:`k_sem_init()`, this API is declared in
``include/kernel.h``. At the bottom of ``include/kernel.h`` is::
Expand All @@ -143,51 +145,22 @@ example of :c:func:`k_sem_init()`, this API is declared in

Inside this header is the body of :c:func:`k_sem_init()`::

K_SYSCALL_DECLARE3_VOID(K_SYSCALL_K_SEM_INIT, k_sem_init, struct k_sem *,
sem, unsigned int, initial_count,
unsigned int, limit);
static inline void k_sem_init(struct k_sem * sem, unsigned int initial_count, unsigned int limit)
{
#ifdef CONFIG_USERSPACE
if (z_syscall_trap()) {
z_arch_syscall_invoke3(*(u32_t *)&sem, *(u32_t *)&initial_count, *(u32_t *)&limit, K_SYSCALL_K_SEM_INIT);
return;
}
compiler_barrier();
#endif
z_impl_k_sem_init(sem, initial_count, limit);
}

This generates an inline function that takes three arguments with void
return value. Depending on context it will either directly call the
implementation function or go through a system call elevation. A
prototype for the implementation function is also automatically generated.
In this example, the implementation of the :c:macro:`K_SYSCALL_DECLARE3_VOID()`
macro will be::

#if !defined(CONFIG_USERSPACE) || defined(__ZEPHYR_SUPERVISOR__)

#define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \
extern void _impl_##name(t0 p0, t1 p1, t2 p2); \
static inline void name(t0 p0, t1 p1, t2 p2) \
{ \
_impl_##name(p0, p1, p2); \
}

#elif defined(__ZEPHYR_USER__)
#define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \
static inline void name(t0 p0, t1 p1, t2 p2) \
{ \
_arch_syscall_invoke3((u32_t)p0, (u32_t)p1, (u32_t)p2, id); \
}

#else /* mixed kernel/user macros */
#define K_SYSCALL_DECLARE3_VOID(id, name, t0, p0, t1, p1, t2, p2) \
extern void _impl_##name(t0 p0, t1 p1, t2 p2); \
static inline void name(t0 p0, t1 p1, t2 p2) \
{ \
if (_is_user_context()) { \
_arch_syscall_invoke3((u32_t)p0, (u32_t)p1, (u32_t)p2, id); \
} else { \
compiler_barrier(); \
_impl_##name(p0, p1, p2); \
} \
}
#endif

The header containing :c:macro:`K_SYSCALL_DECLARE3_VOID()` is itself
generated due to its repetitive nature and can be found in
``include/generated/syscall_macros.h``. It is created by
``scripts/gen_syscall_header.py``.

The final layer is the invocation of the system call itself. All architectures
implementing system calls must implement the seven inline functions
Expand All @@ -197,17 +170,19 @@ necessary privilege elevation. In this layer, all arguments are treated as an
unsigned 32-bit type. There is always a 32-bit unsigned return value, which
may or may not be used.

Some system calls may have more than six arguments. The number of arguments
passed via registers is fixed at six for all architectures. Additional
arguments will need to be passed in a struct, which needs to be treated as
untrusted memory in the handler function. This is done by the derived
functions :c:func:`_syscall_invoke7` through :c:func:`_syscall_invoke10`.
Some system calls may have more than six arguments. The number of
arguments passed via registers is limited to six for all
architectures. Additional arguments will need to be passed in an array
in the source memory space, which needs to be treated as untrusted
memory in the handler function. This code (packing, unpacking and
validation) is generated automatically as needed in the stub above and
in the unmarshalling function.

Some system calls may return a value that will not fit in a 32-bit register,
such as APIs that return a 64-bit value. In this scenario, the return value is
populated in a memory buffer that is passed in as an argument. For example,
see the implementation of :c:func:`_syscall_ret64_invoke0` and
:c:func:`_syscall_ret64_invoke1`.
Some system calls may return a value that will not fit in a 32-bit
register, such as APIs that return a 64-bit value. In this scenario,
the return value is populated in a **untrusted** memory buffer that is
passed in as a final argument. Likewise, this code is generated
automatically.

Implementation Function
***********************
Expand Down Expand Up @@ -312,159 +287,32 @@ If any check fails, the macros will return a nonzero value. The macro
calling thread. This is done instead of returning some error condition to
keep the APIs the same when calling from supervisor mode.

Handler Declaration
Verifier Definition
===================

All handler functions have the same prototype:

.. code-block:: c
u32_t _handler_<API name>(u32_t arg1, u32_t arg2, u32_t arg3,
u32_t arg4, u32_t arg5, u32_t arg6, void *ssf)
All handlers return a value. Handlers are passed exactly six arguments, which
were sent from user mode to the kernel via registers in the
architecture-specific system call implementation, plus an opaque context
pointer which indicates the system state when the system call was invoked from
user code.

To simplify the prototype, the variadic :c:macro:`Z_SYSCALL_HANDLER()` macro
should be used to declare the handler name and names of each argument. Type
information is not necessary since all arguments and the return value are
:c:type:`u32_t`. Using :c:func:`k_sem_init()` as an example:

.. code-block:: c
Z_SYSCALL_HANDLER(k_sem_init, sem, initial_count, limit)
{
...
}
After validating all the arguments, the handler function needs to then call
the implementation function. If the implementation function returns a value,
this needs to be returned by the handler, otherwise the handler should return
0.

.. note:: Do not forget that all the arguments to the handler are passed in as
unsigned 32-bit values. If checks are needed on parameters that are
actually signed values, casts may be needed in order for these checks to
be performed properly.

Using :c:func:`k_sem_init()` as an example again, we need to enforce that the
semaphore object passed in is a valid semaphore object (but not necessarily
initialized), and that the limit parameter is nonzero:

.. code-block:: c
Z_SYSCALL_HANDLER(k_sem_init, sem, initial_count, limit)
{
Z_OOPS(Z_SYSCALL_OBJ_INIT(sem, K_OBJ_SEM));
Z_OOPS(Z_SYSCALL_VERIFY(limit != 0));
_impl_k_sem_init((struct k_sem *)sem, initial_count, limit);
return 0;
}
Simple Handler Declarations
---------------------------

Many kernel or driver APIs have very simple handler functions, where they
either accept no arguments, or take one object which is a kernel object
pointer of some specific type. Some special macros have been defined for
these simple cases, with variants depending on whether the API has a return
value:

* :c:macro:`Z_SYSCALL_HANDLER1_SIMPLE()` one kernel object argument, returns
a value
* :c:macro:`Z_SYSCALL_HANDLER1_SIMPLE_VOID()` one kernel object argument,
no return value
* :c:macro:`Z_SYSCALL_HANDLER0_SIMPLE()` no arguments, returns a value
* :c:macro:`Z_SYSCALL_HANDLER0_SIMPLE_VOID()` no arguments, no return value

For example, :c:func:`k_sem_count_get()` takes a semaphore object as its
only argument and returns a value, so its handler can be completely expressed
as:

.. code-block:: c
Z_SYSCALL_HANDLER1_SIMPLE(k_sem_count_get, K_OBJ_SEM, struct k_sem *);
System Calls With 6 Or More Arguments
=====================================

System calls may have more than six arguments, however the number of arguments
passed in via registers when the privilege elevation is invoked is fixed at six
for all architectures. In this case, the sixth and subsequent arguments to the
system call are placed into a struct, and a pointer to that struct is passed to
the handler as its sixth argument.

See ``include/syscall.h`` to see how this is done; the struct passed in must be
validated like any other memory buffer. For example, for a system call
with nine arguments, arguments 6 through 9 will be passed in via struct, which
must be verified since memory pointers from user mode can be incorrect or
malicious:

.. code-block:: c
Z_SYSCALL_HANDLER(k_foo, arg1, arg2, arg3, arg4, arg5, more_args_ptr)
{
struct _syscall_9_args *margs = (struct _syscall_9_args *)more_args_ptr;
All system calls are dispatched to a verifier function with a prefixed
``z_vrfy_`` name based on the system call. They have exactly the same
return type and argument types as the wrapped system call. Their job
is to execute the system call (generally by calling the implementation
function) after having validated all arguments.

Z_OOPS(Z_SYSCALL_MEMORY_READ(margs, sizeof(*margs)));
The verifier is itself invoked by an automatically generated
unmarshaller function which takes care of unpacking the register
arguments from the architecture layer and casting them to the correct
type. This is defined in a header file that must be included from
user code, generally somewhere after the definition of the verifier in
a translation unit (so that it can be inlined).

...
}
It is also very important to note that arguments passed in this way can change
at any time due to concurrent access to the argument struct. If any parameters
are subject to enforcement checks, they need to be copied out of the struct and
only then checked. One way to ensure this isn't optimized out is to declare the
argument struct as ``volatile``, and copy values out of it into local variables
before checking. Using the previous example:

.. code-block:: c
Z_SYSCALL_HANDLER(k_foo, arg1, arg2, arg3, arg4, arg5, more_args_ptr)
{
volatile struct _syscall_9_args *margs =
(struct _syscall_9_args *)more_args_ptr;
int arg8;
Z_OOPS(Z_SYSCALL_MEMORY_READ(margs, sizeof(*margs)));
arg8 = margs->arg8;
Z_OOPS(Z_SYSCALL_VERIFY_MSG(arg8 < 12, "arg8 must be less than 12"));
_impl_k_foo(arg1, arg2, arg3, arg3, arg4, arg5, margs->arg6,
margs->arg7, arg8, margs->arg9);
return 0;
}
System Calls With 64-bit Return Value
=====================================

If a system call has a return value larger than 32-bits, the handler will not
return anything. Instead, a pointer to a sufficient memory region for the
return value will be passed in as an additional argument. As an example, we
have the system call for getting the current system uptime:

.. code-block:: c
__syscall s64_t k_uptime_get(void);
The handler function has the return area passed in as a pointer, which must
be validated as writable by the calling thread:
For example:

.. code-block:: c
Z_SYSCALL_HANDLER(k_uptime_get, ret_p)
static int z_vrfy_k_sem_take(struct k_sem *sem, s32_t timeout)
{
s64_t *ret = (s64_t *)ret_p;
Z_OOPS(Z_SYSCALL_MEMORY_WRITE(ret, sizeof(*ret)));
*ret = _impl_k_uptime_get();
return 0;
Z_OOPS(Z_SYSCALL_OBJ(sem, K_OBJ_SEM));
return z_impl_k_sem_take(sem, timeout);
}
#include <syscalls/k_sem_take_mrsh.c>
Configuration Options
*********************
Expand All @@ -479,11 +327,6 @@ APIs
Helper macros for creating system call handlers are provided in
:zephyr_file:`kernel/include/syscall_handler.h`:

* :c:macro:`Z_SYSCALL_HANDLER()`
* :c:macro:`Z_SYSCALL_HANDLER1_SIMPLE()`
* :c:macro:`Z_SYSCALL_HANDLER1_SIMPLE_VOID()`
* :c:macro:`Z_SYSCALL_HANDLER0_SIMPLE()`
* :c:macro:`Z_SYSCALL_HANDLER0_SIMPLE_VOID()`
* :c:macro:`Z_SYSCALL_OBJ()`
* :c:macro:`Z_SYSCALL_OBJ_INIT()`
* :c:macro:`Z_SYSCALL_OBJ_NEVER_INIT()`
Expand All @@ -505,10 +348,4 @@ Functions for invoking system calls are defined in
* :c:func:`_arch_syscall_invoke4`
* :c:func:`_arch_syscall_invoke5`
* :c:func:`_arch_syscall_invoke6`
* :c:func:`_syscall_invoke7`
* :c:func:`_syscall_invoke8`
* :c:func:`_syscall_invoke9`
* :c:func:`_syscall_invoke10`
* :c:func:`_syscall_ret64_invoke0`
* :c:func:`_syscall_ret64_invoke1`

6 changes: 4 additions & 2 deletions drivers/ptp_clock/ptp_clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#include <ptp_clock.h>

#ifdef CONFIG_USERSPACE
Z_SYSCALL_HANDLER(ptp_clock_get, dev, tm)
int z_vrfy_ptp_clock_get(struct device *dev,
struct net_ptp_time *tm)
{
struct net_ptp_time ptp_time;
int ret;
Expand All @@ -25,6 +26,7 @@ Z_SYSCALL_HANDLER(ptp_clock_get, dev, tm)
return 0;
}

return (u32_t)ret;
return ret;
}
#include <syscalls/ptp_clock_get_mrsh.c>
#endif /* CONFIG_USERSPACE */
Loading

0 comments on commit 6564974

Please sign in to comment.