From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v2 7/9] box: sandbox option for persistent functions Date: Thu, 6 Jun 2019 15:04:03 +0300 Message-Id: <6b79e42320697259d90dfbc4ed0ff79882a857ee.1559822429.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov List-ID: 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"); + } } else { def->returns = FIELD_TYPE_ANY; def->is_deterministic = false; diff --git a/src/box/errcode.h b/src/box/errcode.h index 9c15f3322..1724e89a9 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -247,6 +247,7 @@ struct errcode_record { /*192 */_(ER_INDEX_DEF_UNSUPPORTED, "%s are prohibited in an index definition") \ /*193 */_(ER_CK_DEF_UNSUPPORTED, "%s are prohibited in a CHECK constraint definition") \ /*194 */_(ER_MULTIKEY_INDEX_MISMATCH, "Field %s is used as multikey in one index and as single key in another") \ + /*195 */_(ER_WRONG_FUNCTION_OPTIONS, "Wrong function options (field %u): %s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/func.c b/src/box/func.c index b21221d9e..f2065422c 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -643,6 +643,14 @@ func_lua_load_cb(struct lua_State *L) func->def->body); goto error; } + if (func->def->opts.is_sandboxed) { + if (luaT_get_sandbox(L) != 0) { + diag_set(ClientError, ER_LOAD_FUNCTION, func->def->name, + func->def->body); + goto error; + } + lua_setfenv(L, -2); + } func->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX); region_truncate(region, region_svp); return lua_gettop(L); 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), +}; diff --git a/src/box/func_def.h b/src/box/func_def.h index 8953e4776..b382cde00 100644 --- a/src/box/func_def.h +++ b/src/box/func_def.h @@ -33,6 +33,7 @@ #include "trivia/util.h" #include "field_def.h" +#include "opt_def.h" #include /** @@ -46,6 +47,19 @@ enum func_language { extern const char *func_language_strs[]; +/** Function options. */ +struct func_opts { + /** + * Whether the routine mast be initialized with isolated + * sandbox where only a limited number if functions is + * available. + */ + bool is_sandboxed; +}; + +extern const struct func_opts func_opts_default; +extern const struct opt_def func_opts_reg[]; + /** * Definition of a function. Function body is not stored * or replicated (yet). @@ -70,6 +84,8 @@ struct func_def { * The language of the stored function. */ enum func_language language; + /** The function options. */ + struct func_opts opts; /** Function name. */ char name[0]; }; @@ -89,6 +105,13 @@ func_def_sizeof(uint32_t name_len, uint32_t body_len) return sz; } +/** Create index options using default values. */ +static inline void +func_opts_create(struct func_opts *opts) +{ + *opts = func_opts_default; +} + static inline bool func_def_is_persistent(struct func_def *def) { 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) +{ + 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; + 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; +} + int tarantool_lua_utils_init(struct lua_State *L) { diff --git a/src/lua/utils.h b/src/lua/utils.h index 81e936bee..36bb2e53b 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -603,6 +603,14 @@ int luaT_func_find(struct lua_State *L, const char *name, const char *name_end, int *count); +/** + * Prepare a new 'default' sandbox table on the top of the Lua + * stack. The 'default' sandbox consists of a minimum set of + * functions that are sufficient to serve persistent functions. + */ +int +luaT_get_sandbox(struct lua_State *L); + int tarantool_lua_utils_init(struct lua_State *L); diff --git a/test/box/misc.result b/test/box/misc.result index 4fcd13a78..a4c8ae498 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -523,6 +523,7 @@ t; 192: box.error.INDEX_DEF_UNSUPPORTED 193: box.error.CK_DEF_UNSUPPORTED 194: box.error.MULTIKEY_INDEX_MISMATCH + 195: box.error.WRONG_FUNCTION_OPTIONS ... test_run:cmd("setopt delimiter ''"); --- diff --git a/test/box/persistent_func.result b/test/box/persistent_func.result index f5e03dd5b..54548f3b0 100644 --- a/test/box/persistent_func.result +++ b/test/box/persistent_func.result @@ -50,6 +50,42 @@ conn:call("test", {{address = "Moscow Dolgoprudny"}}) box.schema.func.create('test2', {body = body, is_deterministic = true}) --- ... +-- Test snadboxed functions. +test_run:cmd("setopt delimiter ';'") +--- +- true +... +body = [[function(number) + math.abs = math.log + return math.abs(number) + end]] +test_run:cmd("setopt delimiter ''"); +--- +... +box.schema.func.create('monkey', {language = 'C', opts = {is_sandboxed = true}}) +--- +- error: 'Failed to create function ''monkey'': is_sandboxed option is applieble only + for persistent Lua function' +... +box.schema.func.create('monkey', {opts = {is_sandboxed = true}}) +--- +- error: 'Failed to create function ''monkey'': is_sandboxed option is applieble only + for persistent Lua function' +... +box.schema.func.create('monkey', {body = body, opts = {is_sandboxed = true}}) +--- +... +conn:call("monkey", {1}) +--- +- 0 +... +math.abs(1) +--- +- 1 +... +box.schema.func.drop('monkey') +--- +... -- Test function with spell error - case 1. test_run:cmd("setopt delimiter ';'") --- diff --git a/test/box/persistent_func.test.lua b/test/box/persistent_func.test.lua index 89b3f6c34..095a27872 100644 --- a/test/box/persistent_func.test.lua +++ b/test/box/persistent_func.test.lua @@ -24,6 +24,21 @@ box.schema.func.exists('test') conn:call("test", {{address = "Moscow Dolgoprudny"}}) box.schema.func.create('test2', {body = body, is_deterministic = true}) +-- Test snadboxed functions. +test_run:cmd("setopt delimiter ';'") +body = [[function(number) + math.abs = math.log + return math.abs(number) + end]] +test_run:cmd("setopt delimiter ''"); + +box.schema.func.create('monkey', {language = 'C', opts = {is_sandboxed = true}}) +box.schema.func.create('monkey', {opts = {is_sandboxed = true}}) +box.schema.func.create('monkey', {body = body, opts = {is_sandboxed = true}}) +conn:call("monkey", {1}) +math.abs(1) +box.schema.func.drop('monkey') + -- Test function with spell error - case 1. test_run:cmd("setopt delimiter ';'") body_bad2 = [[function(tuple) -- 2.21.0