[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