[PATCH v2 7/9] box: sandbox option for persistent functions
Kirill Shcherbatov
kshcherbatov at tarantool.org
Thu Jun 6 15:04:03 MSK 2019
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 <stdbool.h>
/**
@@ -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
More information about the Tarantool-patches
mailing list