[PATCH v2 7/9] box: sandbox option for persistent functions
Vladimir Davydov
vdavydov.dev at gmail.com
Mon Jun 10 17:06:45 MSK 2019
On Thu, Jun 06, 2019 at 03:04:03PM +0300, Kirill Shcherbatov wrote:
> Introduced a new option 'is_sandboxed' to initialize a new
> persistent function inside of isolated sandbox where only limited
> number of functions and modules is available:
> -assert -error -pairs -ipairs -next -pcall -xpcall -type
> -print -select -string -tonumber -tostring -unpack -math -utf8
>
> Global variables are forbidden in persistent Lua functions.
>
> To initialize a new persistent function inside of sandbox,
> specify is_sandboxed = true:
> box.schema.func.create('myfunc', {body = body,
> opts = {is_sandboxed = true}})
>
> Part of #4182
> Needed for #1260
> ---
> src/box/alter.cc | 12 ++++++
> src/box/errcode.h | 1 +
> src/box/func.c | 8 ++++
> src/box/func_def.c | 9 +++++
> src/box/func_def.h | 23 +++++++++++
> src/lua/utils.c | 67 +++++++++++++++++++++++++++++++
> src/lua/utils.h | 8 ++++
> test/box/misc.result | 1 +
> test/box/persistent_func.result | 36 +++++++++++++++++
> test/box/persistent_func.test.lua | 15 +++++++
> 10 files changed, 180 insertions(+)
>
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 11cad77c3..c769e4f3d 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -2449,6 +2449,7 @@ func_def_new_from_tuple(struct tuple *tuple)
> if (def == NULL)
> tnt_raise(OutOfMemory, def_sz, "malloc", "def");
> auto def_guard = make_scoped_guard([=] { free(def); });
> + func_opts_create(&def->opts);
> func_def_get_ids_from_tuple(tuple, &def->fid, &def->uid);
> memcpy(def->name, name, name_len);
> def->name[name_len] = 0;
> @@ -2492,6 +2493,17 @@ func_def_new_from_tuple(struct tuple *tuple)
> }
> def->is_deterministic =
> tuple_field_bool_xc(tuple, BOX_FUNC_FIELD_IS_DETERMINISTIC);
> + const char *opts = tuple_field(tuple, BOX_FUNC_FIELD_OPTS);
> + if (opts_decode(&def->opts, func_opts_reg, &opts,
> + ER_WRONG_FUNCTION_OPTIONS, BOX_FUNC_FIELD_OPTS,
> + NULL) != 0)
> + diag_raise();
> + if (def->opts.is_sandboxed &&
> + (def->language != FUNC_LANGUAGE_LUA || body_len == 0)) {
> + tnt_raise(ClientError, ER_CREATE_FUNCTION, name,
> + "is_sandboxed option is applieble only for "
> + "persistent Lua function");
> + }
Again, the check should be carried out in function constructors so that
we don't change this code when we introduce SQL functions.
> } else {
> def->returns = FIELD_TYPE_ANY;
> def->is_deterministic = false;
> diff --git a/src/box/func_def.c b/src/box/func_def.c
> index 76ed77b24..df74a6d9a 100644
> --- a/src/box/func_def.c
> +++ b/src/box/func_def.c
> @@ -1,3 +1,12 @@
> #include "func_def.h"
> +#include "opt_def.h"
>
> const char *func_language_strs[] = {"LUA", "C"};
> +
> +const struct func_opts func_opts_default = {
> + /* .is_sandboxed = */ false,
> +};
> +
> +const struct opt_def func_opts_reg[] = {
> + OPT_DEF("is_sandboxed", OPT_BOOL, struct func_opts, is_sandboxed),
> +};
AFAIK we've decided to make all function options as tuple fields while
the 'option' field is here just in case we need to add something later.
> diff --git a/src/lua/utils.c b/src/lua/utils.c
> index 27ff6b396..0d1cca423 100644
> --- a/src/lua/utils.c
> +++ b/src/lua/utils.c
> @@ -46,6 +46,14 @@ static uint32_t CTID_STRUCT_IBUF_PTR;
> uint32_t CTID_CHAR_PTR;
> uint32_t CTID_CONST_CHAR_PTR;
>
> +static const char *default_sandbox_exports[] =
> + {"assert", "error", "ipairs", "math", "next", "pairs", "pcall",
> + "print", "select", "string", "table", "tonumber", "tostring",
> + "type", "unpack", "xpcall", "utf8"};
> +
> +static int luaL_deepcopy_func_ref = LUA_REFNIL;
> +static int luaL_default_sandbox_ref = LUA_REFNIL;
> +
> void *
> luaL_pushcdata(struct lua_State *L, uint32_t ctypeid)
> {
> @@ -1248,6 +1256,65 @@ luaT_func_find(struct lua_State *L, const char *name, const char *name_end,
> return 0;
> }
>
> +/**
> + * Assemble a new sandbox with given exports table on top of the
> + * Lua stack. All modules in exports list are copying deeply
> + * to ensure the immutablility of this system object.
> + */
> +static int
> +luaT_prepare_sandbox(struct lua_State *L, const char *exports[],
> + uint32_t exports_count)
> +{
> + assert(luaL_deepcopy_func_ref != LUA_REFNIL);
> + lua_createtable(L, exports_count, 0);
> + for (unsigned i = 0; i < exports_count; i++) {
> + int count;
> + uint32_t name_len = strlen(exports[i]);
> + if (luaT_func_find(L, exports[i], exports[i] + name_len,
> + &count) != 0)
> + return -1;
> + switch (lua_type(L, -1)) {
> + case LUA_TTABLE:
> + lua_rawgeti(L, LUA_REGISTRYINDEX,
> + luaL_deepcopy_func_ref);
> + lua_insert(L, -2);
> + lua_call(L, 1, LUA_MULTRET);
> + FALLTHROUGH;
> + case LUA_TFUNCTION:
> + break;
> + default:
> + unreachable();
> + }
> + lua_setfield(L, -2, exports[i]);
> + }
> + return 0;
> +}
> +
> +int
> +luaT_get_sandbox(struct lua_State *L)
As I mentioned earlier, I don't think we need to have these highly
specialized functions in src/lua/util.[hc]. I'd define them as static
in src/box/lua/call.[hc] as we only need them to invoke persistent Lua
functions.
> +{
> + if (luaL_deepcopy_func_ref == LUA_REFNIL) {
> + int count;
> + const char *deepcopy = "table.deepcopy";
> + if (luaT_func_find(L, deepcopy, deepcopy + strlen(deepcopy),
> + &count) != 0)
> + return -1;
Wouldn't it be more effecient to copy the table in C, i.e. without the
aid of 'table.deepcopy' method?
> + luaL_deepcopy_func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
> + assert(luaL_deepcopy_func_ref != LUA_REFNIL);
> + }
> + if (luaL_default_sandbox_ref == LUA_REFNIL) {
> + if (luaT_prepare_sandbox(L, default_sandbox_exports,
> + nelem(default_sandbox_exports)) != 0)
> + return -1;
> + luaL_default_sandbox_ref = luaL_ref(L, LUA_REGISTRYINDEX);
> + assert(luaL_default_sandbox_ref != LUA_REFNIL);
> + }
> + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_deepcopy_func_ref);
> + lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_default_sandbox_ref);
> + lua_call(L, 1, LUA_MULTRET);
> + return 0;
> +}
More information about the Tarantool-patches
mailing list