[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