ChibiOS/os/sb/host/sbhost.c

525 lines
15 KiB
C

/*
ChibiOS - Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014,
2015,2016,2017,2018,2019,2020,2021 Giovanni Di Sirio.
This file is part of ChibiOS.
ChibiOS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
ChibiOS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file sb/host/sbhost.c
* @brief ARM SandBox host code.
*
* @addtogroup ARM_SANDBOX
* @{
*/
#include <string.h>
#include "ch.h"
#include "sb.h"
/*===========================================================================*/
/* Module local definitions. */
/*===========================================================================*/
#define PUSHSPACE(sp, n) do { \
(sp) = (void *)((uint8_t *)(sp) - (n)); \
} while (false)
#define PUSHTYPE(type, sp, p) do { \
PUSHSPACE(sp, sizeof (type)); \
*(type *)(void *)(sp) = (type)(p); \
} while (false)
/*===========================================================================*/
/* Module exported variables. */
/*===========================================================================*/
/**
* @brief Global sandbox managed state variable.
*/
sb_t sb;
/*===========================================================================*/
/* Module local types. */
/*===========================================================================*/
/*===========================================================================*/
/* Module local variables. */
/*===========================================================================*/
/*===========================================================================*/
/* Module local functions. */
/*===========================================================================*/
/*===========================================================================*/
/* Module exported functions. */
/*===========================================================================*/
size_t sb_strv_getsize(const char *v[], int *np) {
const char* s;
size_t size;
int n;
size = sizeof (const char *);
if (v != NULL) {
n = 0;
while ((s = *v) != NULL) {
size += sizeof (const char *) + strlen(s) + (size_t)1;
n++;
v++;
}
if (np != NULL) {
*np = n;
}
}
return MEM_ALIGN_NEXT(size, MEM_NATURAL_ALIGN);
}
void sb_strv_copy(const char *sp[], void *dp, int n) {
char **vp;
char *cp;
int i;
vp = (char **)dp;
cp = (char *)dp + ((n + 1) * sizeof (char *));
for (i = 0; i < n; i++) {
const char *ss = sp[i];
*(vp + i) = cp;
while ((*cp++ = *ss++) != '\0') {
/* Copy.*/
}
}
*(vp + n) = NULL;
}
bool sb_is_valid_read_range(sb_class_t *sbp, const void *start, size_t size) {
const sb_memory_region_t *rp = &sbp->config->regions[0];
do {
if (rp->used && chMemIsSpaceWithinX(&rp->area, start, size)) {
return true;
}
rp++;
} while (rp < &sbp->config->regions[SB_CFG_NUM_REGIONS]);
return false;
}
bool sb_is_valid_write_range(sb_class_t *sbp, void *start, size_t size) {
const sb_memory_region_t *rp = &sbp->config->regions[0];
do {
if (rp->used && chMemIsSpaceWithinX(&rp->area, start, size)) {
return rp->writeable;
}
rp++;
} while (rp < &sbp->config->regions[SB_CFG_NUM_REGIONS]);
return false;
}
size_t sb_check_string(sb_class_t *sbp, const char *s, size_t max) {
const sb_memory_region_t *rp = &sbp->config->regions[0];
do {
if (rp->used) {
size_t n = chMemIsStringWithinX(&rp->area, s, max);
if (n > (size_t)0) {
return n;
}
}
rp++;
} while (rp < &sbp->config->regions[SB_CFG_NUM_REGIONS]);
return (size_t)0;
}
size_t sb_check_pointers_array(sb_class_t *sbp, const void *pp[], size_t max) {
const sb_memory_region_t *rp = &sbp->config->regions[0];
do {
if (rp->used) {
size_t an = chMemIsPointersArrayWithinX(&rp->area, pp, max);
if (an > (size_t)0) {
return an;
}
}
rp++;
} while (rp < &sbp->config->regions[SB_CFG_NUM_REGIONS]);
return (size_t)0;
}
size_t sb_check_strings_array(sb_class_t *sbp, const char *pp[], size_t max) {
const char *s;
size_t n;
n = sb_check_pointers_array(sbp, (const void **)pp, max);
if (n > (size_t)0) {
while ((s = *pp++) != NULL) {
size_t sn;
sn = sb_check_string(sbp, s, max - n);
if (sn == (size_t)0) {
return (size_t)0;
}
n += sn;
}
}
return n;
}
/**
* @brief Sandbox object initialization.
*
* @param[out] sbp pointer to the sandbox object
* @param[in] config pointer to the sandbox configuration
*
* @init
*/
void sbObjectInit(sb_class_t *sbp, const sb_config_t *config) {
memset((void *)sbp, 0, sizeof (sb_class_t));
sbp->config = config;
}
/**
* @brief Starts a sandboxed thread.
*
* @param[in] sbp pointer to a @p sb_class_t structure
* @param[in] argv array of parameters for the sandbox
* @param[in] envp array of environment variables for the sandbox
* @return The thread pointer.
* @retval NULL if the sandbox thread creation failed.
*/
thread_t *sbStartThread(sb_class_t *sbp,
const char *argv[],
const char *envp[]) {
thread_t *utp;
const sb_config_t *config = sbp->config;
void *usp, *uargv, *uenvp;
size_t envsize, argsize, parsize;
int uargc, uenvc;
/* Header location.*/
sbp->sbhp = (const sb_header_t *)(void *)config->regions[config->code_region].area.base;
/* Checking header magic numbers.*/
if ((sbp->sbhp->hdr_magic1 != SB_HDR_MAGIC1) ||
(sbp->sbhp->hdr_magic2 != SB_HDR_MAGIC2)) {
return NULL;
}
/* Checking header size and alignment.*/
if (sbp->sbhp->hdr_size != sizeof (sb_header_t)) {
return NULL;
}
/* Checking header entry point.*/
if (!chMemIsSpaceWithinX(&config->regions[config->code_region].area,
(const void *)sbp->sbhp->hdr_entry,
(size_t)2)) {
return NULL;
}
/* Setting up an initial stack for the sandbox.*/
usp = (config->regions[config->data_region].area.base +
config->regions[config->data_region].area.size);
/* Allocating space for environment variables.*/
envsize = sb_strv_getsize(envp, &uenvc);
PUSHSPACE(usp, envsize);
uenvp = usp;
/* Allocating space for arguments.*/
argsize = sb_strv_getsize(argv, &uargc);
PUSHSPACE(usp, argsize);
uargv = usp;
/* Allocating space for parameters.*/
if (MEM_IS_ALIGNED(usp, PORT_STACK_ALIGN)) {
parsize = sizeof (uint32_t) * 4;
}
else {
parsize = sizeof (uint32_t) * 5;
}
PUSHSPACE(usp, parsize);
/* Checking stack allocation.*/
if (!chMemIsSpaceWithinX(&config->regions[config->data_region].area,
usp, envsize + argsize + parsize)) {
return NULL;
}
/* Initializing stack.*/
sb_strv_copy(envp, uenvp, uenvc);
sb_strv_copy(argv, uargv, uargc);
*((uint32_t *)usp + 4) = (uint32_t)0x55555555;
*((uint32_t *)usp + 2) = (uint32_t)uenvp;
*((uint32_t *)usp + 1) = (uint32_t)uargv;
*((uint32_t *)usp + 0) = (uint32_t)uargc;
unprivileged_thread_descriptor_t utd = {
.name = config->thread.name,
.wbase = (stkalign_t *)config->thread.wsp,
.wend = (stkalign_t *)config->thread.wsp +
(config->thread.size / sizeof (stkalign_t)),
.prio = config->thread.prio,
.u_pc = sbp->sbhp->hdr_entry,
.u_psp = (uint32_t)usp,
.arg = (void *)sbp
};
#if PORT_SWITCHED_REGIONS_NUMBER > 0
for (unsigned i = 0U; i < PORT_SWITCHED_REGIONS_NUMBER; i++) {
utd.regions[i] = config->mpuregs[i];
}
#endif
utp = chThdCreateUnprivileged(&utd);
/* For messages exchange.*/
sbp->tp = utp;
return utp;
}
/**
* @brief Verifies if the sandbox thread is running.
*
* @param[in] sbp pointer to a @p sb_class_t structure
* @return The thread status.
*
* @api
*/
bool sbIsThreadRunningX(sb_class_t *sbp) {
if (sbp->tp == NULL) {
return false;
}
return !chThdTerminatedX(sbp->tp);
}
#if (SB_CFG_ENABLE_VFS == TRUE) || defined(__DOXYGEN__)
/**
* @brief Execute an elf file within a sandbox.
* @note The file is loaded into region zero of the sandbox which is
* assumed to be used for both code and data, extra regions are
* not touched by this function.
*
* @param[in] sbp pointer to a @p sb_class_t structure
* @param[in] pathname file to be executed
* @param[in] argv arguments to be passed to the sandbox
* @param[in] envp environment variables to be passed to the sandbox
* @return The operation result.
*
* @api
*/
msg_t sbExec(sb_class_t *sbp, const char *pathname,
const char *argv[], const char *envp[]) {
const sb_config_t *config = sbp->config;
memory_area_t ma = config->regions[0].area;
msg_t ret;
void *usp, *uargv, *uenvp;
size_t envsize, argsize, parsize;
int uargc, uenvc;
/* Setting up an initial stack for the sandbox.*/
usp = (void *)(config->regions[0].area.base + config->regions[0].area.size);
/* Allocating space for environment variables.*/
envsize = sb_strv_getsize(envp, &uenvc);
PUSHSPACE(usp, envsize);
uenvp = usp;
/* Allocating space for arguments.*/
argsize = sb_strv_getsize(argv, &uargc);
PUSHSPACE(usp, argsize);
uargv = usp;
/* Allocating space for parameters.*/
if (MEM_IS_ALIGNED(usp, PORT_STACK_ALIGN)) {
parsize = sizeof (uint32_t) * 4;
}
else {
parsize = sizeof (uint32_t) * 5;
}
PUSHSPACE(usp, parsize);
/* Checking stack allocation.*/
if (!chMemIsSpaceWithinX(&config->regions[0].area,
usp, envsize + argsize + parsize)) {
return CH_RET_ENOMEM;
}
/* Adjusting the size of the memory area object, we don't want the loaded
elf file to overwrite the initialized stack.*/
ma.size -= envsize + argsize + parsize;
/* Initializing stack.*/
sb_strv_copy(envp, uenvp, uenvc);
sb_strv_copy(argv, uargv, uargc);
*((uint32_t *)usp + 4) = (uint32_t)0x55555555;
*((uint32_t *)usp + 2) = (uint32_t)uenvp;
*((uint32_t *)usp + 1) = (uint32_t)uargv;
*((uint32_t *)usp + 0) = (uint32_t)uargc;
/* Loading sandbox code into the specified memory area.*/
ret = sbElfLoadFile(config->vfs_driver, pathname, &ma);
CH_RETURN_ON_ERROR(ret);
/* Header location.*/
sbp->sbhp = (const sb_header_t *)(void *)ma.base;
/* Checking header magic numbers.*/
if ((sbp->sbhp->hdr_magic1 != SB_HDR_MAGIC1) ||
(sbp->sbhp->hdr_magic2 != SB_HDR_MAGIC2)) {
return CH_RET_ENOEXEC;
}
/* Checking header size.*/
if (sbp->sbhp->hdr_size != sizeof (sb_header_t)) {
return CH_RET_ENOEXEC;
}
/* Checking header entry point.*/
if (!chMemIsSpaceWithinX(&ma, (const void *)sbp->sbhp->hdr_entry, (size_t)2)) {
return CH_RET_EFAULT;
}
/* Everything OK, starting the unprivileged thread inside the sandbox.*/
unprivileged_thread_descriptor_t utd = {
.name = config->thread.name,
.wbase = (stkalign_t *)config->thread.wsp,
.wend = (stkalign_t *)config->thread.wsp +
(config->thread.size / sizeof (stkalign_t)),
.prio = config->thread.prio,
.u_pc = sbp->sbhp->hdr_entry,
.u_psp = (uint32_t)usp,
.arg = (void *)sbp
};
#if PORT_SWITCHED_REGIONS_NUMBER > 0
for (unsigned i = 0U; i < PORT_SWITCHED_REGIONS_NUMBER; i++) {
utd.regions[i] = config->mpuregs[i];
}
#endif
sbp->tp = chThdCreateUnprivileged(&utd);
if (sbp->tp == NULL) {
return CH_RET_ENOMEM;
}
return CH_RET_SUCCESS;
}
/**
* @brief Registers a file descriptor on a sandbox.
*
* @param[in] sbp pointer to a @p sb_class_t structure
* @param[in] fd file descriptor to be assigned
* @param[in] np VFS node to be registered on the file descriptor
*
* @api
*/
void sbRegisterDescriptor(sb_class_t *sbp, int fd, vfs_node_c *np) {
chDbgAssert(sb_is_available_descriptor(&sbp->io, fd), "invalid file descriptor");
sbp->io.vfs_nodes[fd] = np;
}
#endif
#if (CH_CFG_USE_WAITEXIT == TRUE) || defined(__DOXYGEN__)
/**
* @brief Blocks the execution of the invoking thread until the specified
* sandbox thread terminates then the exit code is returned.
* @pre The configuration option @p CH_CFG_USE_WAITEXIT must be enabled in
* order to use this function.
*
* @param[in] sbp pointer to a @p sb_class_t structure
* @return The exit code from the terminated sandbox thread.
* @retval MSG_RESET Sandbox thread not started.
*
* @api
*/
msg_t sbWaitThread(sb_class_t *sbp) {
msg_t msg;
if (sbp->tp == NULL) {
return MSG_RESET;
}
msg = chThdWait(sbp->tp);
sbp->tp = NULL;
return msg;
}
#endif
#if (CH_CFG_USE_MESSAGES == TRUE) || defined(__DOXYGEN__)
/**
* @brief Sends a message to a sandboxed thread.
*
* @param[in] sbp pointer to the sandbox object
* @param[in] msg message to be sent
* @param[in] timeout the number of ticks before the operation timeouts,
* the following special values are allowed:
* - @a TIME_INFINITE no timeout.
* .
* @return The returned message.
* @retval MSG_TIMEOUT if a timeout occurred.
* @retval MSG_RESET if the exchange aborted, sandboxed thread API usage
* error.
*
* @api
*/
msg_t sbSendMessageTimeout(sb_class_t *sbp,
msg_t msg,
sysinterval_t timeout) {
thread_t *ctp = __sch_get_currthread();
chDbgCheck(sbp != NULL);
chSysLock();
/* Sending the message.*/
ctp->u.sentmsg = msg;
__ch_msg_insert(&sbp->tp->msgqueue, ctp);
if (sbp->tp->state == CH_STATE_WTMSG) {
(void) chSchReadyI(sbp->tp);
}
msg = chSchGoSleepTimeoutS(CH_STATE_SNDMSGQ, timeout);
/* If a timeout occurred while the boxed thread already received the message
then this thread needs to "unregister" as sender, the boxed error will
get SB_ERR_EBUSY when/if trying to reply.*/
if ((msg == MSG_TIMEOUT) && (sbp->msg_tp == ctp)) {
sbp->msg_tp = NULL;
}
chSysUnlock();
return msg;
}
#endif /* CH_CFG_USE_MESSAGES == TRUE */
/** @} */