From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v1 5/8] netbox: call persistent functions in netbox Date: Thu, 30 May 2019 13:45:32 +0300 Message-Id: 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: 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