[PATCH v1 5/8] netbox: call persistent functions in netbox

Kirill Shcherbatov kshcherbatov at tarantool.org
Thu May 30 13:45:32 MSK 2019


This patch makes persistent Lua functions are available via
net.box.connect() :call method.

Example:
lua_code = [[function(a, b) return a + b end]]
box.schema.func.create('sum', {body = lua_code,
                       opts = {is_deterministic = true}})
conn:call("sum", {1, 3})

Part of #4182
Needed for #1260
---
 src/box/call.c                    |  2 +-
 src/box/lua/call.c                | 36 ++++++++++++-
 src/box/lua/call.h                |  3 +-
 test/box/persistent_func.result   | 84 +++++++++++++++++++++++++++++++
 test/box/persistent_func.test.lua | 36 +++++++++++++
 5 files changed, 157 insertions(+), 4 deletions(-)

diff --git a/src/box/call.c b/src/box/call.c
index 56da53fb3..773b914b1 100644
--- a/src/box/call.c
+++ b/src/box/call.c
@@ -202,7 +202,7 @@ box_process_call(struct call_request *request, struct port *port)
 	if (func && func->def->language == FUNC_LANGUAGE_C) {
 		rc = box_c_call(func, request, port);
 	} else {
-		rc = box_lua_call(request, port);
+		rc = box_lua_call(func, request, port);
 	}
 	/* Restore the original user */
 	if (orig_credentials)
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index c729778c4..2cd982ba8 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -32,6 +32,8 @@
 #include "box/call.h"
 #include "box/error.h"
 #include "fiber.h"
+#include "box/func.h"
+#include "box/schema.h"
 
 #include "lua/utils.h"
 #include "lua/msgpack.h"
@@ -253,6 +255,31 @@ execute_lua_call(lua_State *L)
 	return lua_gettop(L);
 }
 
+static int
+execute_persistent_function(lua_State *L)
+{
+	struct call_request *request = (struct call_request *)
+		lua_topointer(L, 1);
+	lua_settop(L, 0);
+
+	const char *name = request->name;
+	uint32_t name_len = mp_decode_strl(&name);
+	struct func *func = func_by_name(name, name_len);
+	assert(func != NULL);
+	assert(func->lua_func_ref != LUA_REFNIL);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, func->lua_func_ref);
+
+	/* Push the rest of args (a tuple). */
+	const char *args = request->args;
+	uint32_t arg_count = mp_decode_array(&args);
+	luaL_checkstack(L, arg_count, "lua_func: out of stack");
+	for (uint32_t i = 0; i < arg_count; i++)
+		luamp_decode(L, luaL_msgpack_default, &args);
+
+	lua_call(L, arg_count, LUA_MULTRET);
+	return lua_gettop(L);
+}
+
 static int
 execute_lua_eval(lua_State *L)
 {
@@ -396,9 +423,14 @@ box_process_lua(struct call_request *request, struct port *base,
 }
 
 int
-box_lua_call(struct call_request *request, struct port *port)
+box_lua_call(struct func *func, struct call_request *request, struct port *port)
 {
-	return box_process_lua(request, port, execute_lua_call);
+	if (func != NULL && func_def_is_persistent(func->def)) {
+		return box_process_lua(request, port,
+				       execute_persistent_function);
+	} else {
+		return box_process_lua(request, port, execute_lua_call);
+	}
 }
 
 int
diff --git a/src/box/lua/call.h b/src/box/lua/call.h
index 0542123da..06bcfe77e 100644
--- a/src/box/lua/call.h
+++ b/src/box/lua/call.h
@@ -42,13 +42,14 @@ box_lua_call_init(struct lua_State *L);
 
 struct port;
 struct call_request;
+struct func;
 
 /**
  * Invoke a Lua stored procedure from the binary protocol
  * (implementation of 'CALL' command code).
  */
 int
-box_lua_call(struct call_request *request, struct port *port);
+box_lua_call(struct func *func, struct call_request *request, struct port *port);
 
 int
 box_lua_eval(struct call_request *request, struct port *port);
diff --git a/test/box/persistent_func.result b/test/box/persistent_func.result
index 0644de7fe..1b3eaa8b2 100644
--- a/test/box/persistent_func.result
+++ b/test/box/persistent_func.result
@@ -7,6 +7,15 @@ test_run = env.new()
 --
 -- gh-4182: Add persistent LUA functions.
 --
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+net = require('net.box')
+---
+...
+conn = net.connect(box.cfg.listen)
+---
+...
 -- Test valid function.
 test_run:cmd("setopt delimiter ';'")
 ---
@@ -34,9 +43,58 @@ box.schema.func.exists('test')
 ---
 - true
 ...
+conn:call("test", {{address = "Moscow Dolgoprudny"}})
+---
+- [['moscow'], ['dolgoprudny']]
+...
 box.schema.func.create('test2', {body = body, is_deterministic = true})
 ---
 ...
+-- Test that monkey-patch attack is not possible.
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+body_monkey = [[function(tuple)
+	math.abs = math.log
+	return tuple
+end
+]]
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create('body_monkey', {body = body_monkey})
+---
+...
+conn:call("body_monkey", {{address = "Moscow Dolgoprudny"}})
+---
+- {'address': 'Moscow Dolgoprudny'}
+...
+math.abs(-666.666)
+---
+- 666.666
+...
+-- Test taht 'require' is forbidden.
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+body_bad1 = [[function(tuple)
+	local json = require('json')
+	return json.encode(tuple)
+end
+]]
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.schema.func.create('json_serializer', {body = body_bad1})
+---
+...
+conn:call("json_serializer", {{address = "Moscow Dolgoprudny"}})
+---
+- error: '[string "return function(tuple) ..."]:1: attempt to call global ''require''
+    (a nil value)'
+...
 -- Test function with spell error - case 1.
 test_run:cmd("setopt delimiter ';'")
 ---
@@ -71,6 +129,13 @@ box.schema.func.create('body_bad3', {body = body_bad3})
 - error: "Failed to dynamically load function 'body_bad3': func(tuple) \treturn tuple
     end "
 ...
+conn:call("body_bad3", {{address = "Moscow Dolgoprudny"}})
+---
+- error: Procedure 'body_bad3' is not defined
+...
+conn:close()
+---
+...
 -- Restart server.
 test_run:cmd("restart server default")
 net = require('net.box')
@@ -79,6 +144,16 @@ net = require('net.box')
 test_run = require('test_run').new()
 ---
 ...
+conn = net.connect(box.cfg.listen)
+---
+...
+conn:call("test", {{address = "Moscow Dolgoprudny"}})
+---
+- [['moscow'], ['dolgoprudny']]
+...
+conn:close()
+---
+...
 box.schema.func.exists('test')
 ---
 - true
@@ -90,6 +165,15 @@ box.schema.func.exists('test')
 ---
 - false
 ...
+box.schema.func.drop('body_monkey')
+---
+...
+box.schema.func.drop('json_serializer')
+---
+...
 box.schema.func.drop('test2')
 ---
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
diff --git a/test/box/persistent_func.test.lua b/test/box/persistent_func.test.lua
index 37a761d32..363d687ce 100644
--- a/test/box/persistent_func.test.lua
+++ b/test/box/persistent_func.test.lua
@@ -4,6 +4,10 @@ test_run = env.new()
 --
 -- gh-4182: Add persistent LUA functions.
 --
+box.schema.user.grant('guest', 'execute', 'universe')
+net = require('net.box')
+conn = net.connect(box.cfg.listen)
+
 -- Test valid function.
 test_run:cmd("setopt delimiter ';'")
 body = [[function(tuple)
@@ -17,8 +21,32 @@ test_run:cmd("setopt delimiter ''");
 box.schema.func.create('test', {body = body, language = "C"})
 box.schema.func.create('test', {body = body})
 box.schema.func.exists('test')
+conn:call("test", {{address = "Moscow Dolgoprudny"}})
 box.schema.func.create('test2', {body = body, is_deterministic = true})
 
+-- Test that monkey-patch attack is not possible.
+test_run:cmd("setopt delimiter ';'")
+body_monkey = [[function(tuple)
+	math.abs = math.log
+	return tuple
+end
+]]
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create('body_monkey', {body = body_monkey})
+conn:call("body_monkey", {{address = "Moscow Dolgoprudny"}})
+math.abs(-666.666)
+
+-- Test taht 'require' is forbidden.
+test_run:cmd("setopt delimiter ';'")
+body_bad1 = [[function(tuple)
+	local json = require('json')
+	return json.encode(tuple)
+end
+]]
+test_run:cmd("setopt delimiter ''");
+box.schema.func.create('json_serializer', {body = body_bad1})
+conn:call("json_serializer", {{address = "Moscow Dolgoprudny"}})
+
 -- Test function with spell error - case 1.
 test_run:cmd("setopt delimiter ';'")
 body_bad2 = [[function(tuple)
@@ -36,12 +64,20 @@ end
 ]]
 test_run:cmd("setopt delimiter ''");
 box.schema.func.create('body_bad3', {body = body_bad3})
+conn:call("body_bad3", {{address = "Moscow Dolgoprudny"}})
 
+conn:close()
 -- Restart server.
 test_run:cmd("restart server default")
 net = require('net.box')
 test_run = require('test_run').new()
+conn = net.connect(box.cfg.listen)
+conn:call("test", {{address = "Moscow Dolgoprudny"}})
+conn:close()
 box.schema.func.exists('test')
 box.schema.func.drop('test')
 box.schema.func.exists('test')
+box.schema.func.drop('body_monkey')
+box.schema.func.drop('json_serializer')
 box.schema.func.drop('test2')
+box.schema.user.revoke('guest', 'execute', 'universe')
-- 
2.21.0




More information about the Tarantool-patches mailing list