From: Jo Johnson Date: Wed, 20 Dec 2023 20:14:03 +0000 (-0800) Subject: lua: Add lua sandbox for detection rules X-Git-Tag: suricata-8.0.0-beta1~1265 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8428b0b9d766c5dec376113b4c4b10b3a7cc095c;p=thirdparty%2Fsuricata.git lua: Add lua sandbox for detection rules --- diff --git a/src/Makefile.am b/src/Makefile.am index 65ebc02166..878138539f 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -562,6 +562,7 @@ noinst_HEADERS = \ util-lua-hassh.h \ util-lua-http.h \ util-lua-ja3.h \ + util-lua-sandbox.h \ util-lua-smtp.h \ util-lua-ssh.h \ util-lua-tls.h \ @@ -1155,6 +1156,7 @@ libsuricata_c_a_SOURCES = \ util-lua-hassh.c \ util-lua-http.c \ util-lua-ja3.c \ + util-lua-sandbox.c \ util-lua-smtp.c \ util-lua-ssh.c \ util-lua-tls.c \ diff --git a/src/detect-lua.c b/src/detect-lua.c index a88f7e79a8..023aee82ba 100644 --- a/src/detect-lua.c +++ b/src/detect-lua.c @@ -83,6 +83,7 @@ void DetectLuaRegister(void) #else /* HAVE_LUA */ #include "util-lua.h" +#include "util-lua-sandbox.h" static int DetectLuaMatch (DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *); @@ -97,6 +98,9 @@ static void DetectLuaRegisterTests(void); static void DetectLuaFree(DetectEngineCtx *, void *); static int g_smtp_generic_list_id = 0; +// TODO: move to config +static const uint64_t g_lua_alloc_limit = 500000, g_lua_instruction_limit = 500000; + /** * \brief Registration function for keyword: lua */ @@ -479,7 +483,7 @@ static void *DetectLuaThreadInit(void *data) t->alproto = lua->alproto; t->flags = lua->flags; - t->luastate = LuaGetState(); + t->luastate = sb_newstate(g_lua_alloc_limit, g_lua_instruction_limit); if (t->luastate == NULL) { SCLogError("luastate pool depleted"); goto error; @@ -525,7 +529,7 @@ static void *DetectLuaThreadInit(void *data) error: if (t->luastate != NULL) - LuaReturnState(t->luastate); + sb_close(t->luastate); SCFree(t); return NULL; } @@ -535,7 +539,7 @@ static void DetectLuaThreadFree(void *ctx) if (ctx != NULL) { DetectLuaThreadData *t = (DetectLuaThreadData *)ctx; if (t->luastate != NULL) - LuaReturnState(t->luastate); + sb_close(t->luastate); SCFree(t); } } @@ -581,10 +585,10 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const { int status; - lua_State *luastate = luaL_newstate(); + lua_State *luastate = sb_newstate(g_lua_alloc_limit, g_lua_instruction_limit); if (luastate == NULL) return -1; - luaL_openlibs(luastate); + luaL_openlibs(luastate); // TODO: get sandbox config and load appropriate libs /* hackish, needed to allow unittests to pass buffers as scripts instead of files */ #ifdef UNITTESTS @@ -863,10 +867,10 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const /* pop the table */ lua_pop(luastate, 1); - lua_close(luastate); + sb_close(luastate); return 0; error: - lua_close(luastate); + sb_close(luastate); return -1; } diff --git a/src/detect-lua.h b/src/detect-lua.h index 4f3834087f..104c631fe3 100644 --- a/src/detect-lua.h +++ b/src/detect-lua.h @@ -27,6 +27,7 @@ #ifdef HAVE_LUA #include "util-lua.h" +#include "util-lua-sandbox.h" typedef struct DetectLuaThreadData { lua_State *luastate; diff --git a/src/util-lua-sandbox.c b/src/util-lua-sandbox.c new file mode 100644 index 0000000000..0fcb1dd2c1 --- /dev/null +++ b/src/util-lua-sandbox.c @@ -0,0 +1,299 @@ +/* Copyright (C) 2014-2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jo Johnson + */ + +#include "suricata-common.h" + +#ifdef HAVE_LUA + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + +#include "util-lua-sandbox.h" + +typedef struct sb_block_function { + const char *module; + const char *name; +} sb_block_function; + +static void sb_hook(lua_State *L, lua_Debug *ar); +LUAMOD_API int luaopen_sandbox(lua_State *L); + +static void *sb_alloc(void *ud, void *ptr, size_t osize, size_t nsize) +{ + (void)ud; + (void)osize; /* not used */ + sb_lua_state *ctx = (sb_lua_state *)ud; + if (nsize == 0) { + if (ptr != NULL) { + // ASSERT: alloc_bytes > osize + ctx->alloc_bytes -= osize; + } + SCFree(ptr); + return NULL; + } else { + // We can be a bit sloppy on the alloc limit since it's not supposed to be hit. + // ASSERT: ctx->alloc_bytes + nsize > ctx->alloc_bytes + if (ctx->alloc_bytes + nsize > ctx->alloc_limit) { + // TODO: Trace in a better way + return NULL; + } + void *nptr = SCRealloc(ptr, nsize); + + ctx->alloc_bytes += nsize; + return nptr; + } +} + +/* +** These are the set of libs allowed in the restricted lua sandbox +*/ +static const luaL_Reg sb_restrictedlibs[] = { { LUA_GNAME, luaopen_base }, + // {LUA_LOADLIBNAME, luaopen_package}, + // {LUA_COLIBNAME, luaopen_coroutine}, + { LUA_TABLIBNAME, luaopen_table }, + //{LUA_IOLIBNAME, luaopen_io}, + // {LUA_OSLIBNAME, luaopen_os}, + { LUA_STRLIBNAME, luaopen_string }, { LUA_MATHLIBNAME, luaopen_math }, + { LUA_UTF8LIBNAME, luaopen_utf8 }, + { LUA_DBLIBNAME, luaopen_sandbox }, // TODO: remove this from restricted + { NULL, NULL } }; + +// TODO: should we block raw* functions? +// TODO: Will we ever need to block a subset of functions more than one level deep? +static const sb_block_function sb_restrictedfuncs[] = { { LUA_GNAME, "collectgarbage" }, + { LUA_GNAME, "dofile" }, { LUA_GNAME, "getmetatable" }, { LUA_GNAME, "loadfile" }, + { LUA_GNAME, "load" }, { LUA_GNAME, "pcall" }, { LUA_GNAME, "setmetatable" }, + { LUA_GNAME, "xpcall" }, + + { LUA_STRLIBNAME, "rep" }, // TODO: probably don't need to block this for normal restricted + // since we have memory limit + { NULL, NULL } }; + +static void sb_loadlibs(lua_State *L, const luaL_Reg *libs) +{ + const luaL_Reg *lib; + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib = libs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } +} + +static void sb_block_functions(lua_State *L, const sb_block_function *funcs) +{ + const sb_block_function *func; + + // set target functions to nil + lua_pushglobaltable(L); + for (func = funcs; func->module; func++) { + lua_pushstring(L, func->module); + lua_gettable(L, -2); // load module to stack + lua_pushstring(L, func->name); + lua_pushnil(L); + lua_settable(L, -3); + lua_pop(L, 1); // remove module from the stack + } + lua_pop(L, 1); // remove global table +} + +LUALIB_API void sb_loadrestricted(lua_State *L) +{ + sb_loadlibs(L, sb_restrictedlibs); + sb_block_functions(L, sb_restrictedfuncs); +} + +lua_State *sb_newstate(uint64_t alloclimit, uint64_t instructionlimit) +{ + sb_lua_state *sb = SCMalloc(sizeof(sb_lua_state)); + if (sb == NULL) { + // Out of memory. Error code? + return NULL; + } + + sb->alloc_limit = alloclimit; + sb->alloc_bytes = 0; + sb->hook_instruction_count = 100; + sb->instruction_limit = instructionlimit; + + sb->L = lua_newstate(sb_alloc, sb); /* create state */ + if (sb->L == NULL) { + // TODO: log or error code? + free(sb); + return NULL; + } + + lua_pushstring(sb->L, SANDBOX_CTX); + lua_pushlightuserdata(sb->L, sb); + lua_settable(sb->L, LUA_REGISTRYINDEX); + + lua_sethook(sb->L, sb_hook, LUA_MASKCOUNT, sb->hook_instruction_count); + return sb->L; +} + +static sb_lua_state *sb_get_ctx(lua_State *L) +{ + lua_pushstring(L, SANDBOX_CTX); + lua_gettable(L, LUA_REGISTRYINDEX); + sb_lua_state *ctx = lua_touserdata(L, -1); + // TODO: log if null? + lua_pop(L, 1); + return ctx; +} + +void sb_close(lua_State *L) +{ + sb_lua_state *sb = sb_get_ctx(L); + lua_close(sb->L); + SCFree(sb); +} + +static void sb_hook(lua_State *L, lua_Debug *ar) +{ + (void)ar; + sb_lua_state *sb = sb_get_ctx(L); + + sb->instruction_count += sb->hook_instruction_count; + + if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) { + // TODO: do we care enough for a full traceback here? + luaL_error(L, "Instruction limit exceeded"); + } +} + +void sb_resetinstructioncounter(lua_State *L) +{ + sb_lua_state *sb = sb_get_ctx(L); + if (sb != NULL) { + sb->instruction_count = 0; + lua_sethook(L, sb_hook, LUA_MASKCOUNT, sb->hook_instruction_count); + } +} + +void sb_setinstructionlimit(lua_State *L, uint64_t instruction_limit) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + ctx->instruction_limit = instruction_limit; + } +} + +static uint64_t sb_getinstructioncount(lua_State *L) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + return ctx->instruction_count; + } + return 0; +} + +static int sb_Ltotalalloc(lua_State *L) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + lua_pushinteger(L, ctx->alloc_bytes); + } else { + lua_pushinteger(L, 0); + } + return 1; +} + +static int sb_Lgetalloclimit(lua_State *L) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + lua_pushinteger(L, ctx->alloc_limit); + } else { + lua_pushinteger(L, 0); + } + return 1; +} + +static int sb_Lsetalloclimit(lua_State *L) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + ctx->alloc_limit = luaL_checkinteger(L, 1); + } + return 0; +} + +/* +static int sb_Lsetlevel(lua_State *L) +{ + lua_Integer level = luaL_checkinteger(L, 1); + lua_pushnil(L); + lua_setglobal(L, "debug"); + return 0; +} +*/ + +static int sb_Lgetinstructioncount(lua_State *L) +{ + lua_pushinteger(L, sb_getinstructioncount(L)); + return 1; +} + +static int sb_Lgetinstructionlimit(lua_State *L) +{ + sb_lua_state *ctx = sb_get_ctx(L); + if (ctx != NULL) { + lua_pushinteger(L, ctx->instruction_limit); + } else { + lua_pushinteger(L, 0); + } + return 1; +} + +static int sb_Lsetinstructionlimit(lua_State *L) +{ + sb_setinstructionlimit(L, luaL_checkinteger(L, 1)); + return 0; +} + +static int sb_Lresetinstructioncount(lua_State *L) +{ + sb_resetinstructioncounter(L); + return 0; +} + +static const luaL_Reg sblib[] = { { "totalalloc", sb_Ltotalalloc }, + { "getalloclimit", sb_Lgetalloclimit }, { "setalloclimit", sb_Lsetalloclimit }, + // { "setlevel", sb_Lsetlevel }, + { "instructioncount", sb_Lgetinstructioncount }, + { "getinstructionlimit", sb_Lgetinstructionlimit }, + { "setinstructionlimit", sb_Lsetinstructionlimit }, + { "resetinstructioncount", sb_Lresetinstructioncount }, { NULL, NULL } }; + +LUAMOD_API int luaopen_sandbox(lua_State *L) +{ + luaL_newlib(L, sblib); + return 1; +} + +#endif \ No newline at end of file diff --git a/src/util-lua-sandbox.h b/src/util-lua-sandbox.h new file mode 100644 index 0000000000..42ad7facf8 --- /dev/null +++ b/src/util-lua-sandbox.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2014-2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jo Johnson + */ + +#ifndef __UTIL_LUA_SANDBOX_H__ +#define __UTIL_LUA_SANDBOX_H__ + +#ifndef HAVE_LUA +/* If we don't have Lua, create a typedef for sb_lua_State so the + * exported Lua functions don't fail the build. */ +typedef void sb_lua_state; +#else +#include +#include "lua.h" + +#if !defined(SANDBOX_ALLOC_CTX) +#define SANDBOX_CTX "SANDBOX_CTX" +#endif + +/* + * Lua sandbox usage: The only needed changes to use the sandboxed lua state are + * to replace calls to lua_newstate and lua_close with sb_newstate and sb_close + * Additionally, sb_loadrestricted can be used to load a restricted set of packages + * that prevent side effecting outside of the lua runtime + */ + +/* + * Struct to store a lua_state and the additional metadata required to sandbox it + */ +typedef struct sb_lua_state { + lua_State *L; + + // Allocation limits + uint64_t alloc_bytes; + uint64_t alloc_limit; + + // Execution Limits + uint64_t instruction_count; + uint64_t instruction_limit; + uint64_t hook_instruction_count; +} sb_lua_state; + +enum sb_level { NONE, EXTERNAL_RESTRICTED, PERFORMANCE_RESTRICTED }; + +/* + * Replaces luaL_newstate. Sets an upper bound for allocations and bytecode + * instructions for the lua runtime on this state. + * + * alloclimit - maximium number of bytes lua can allocate before receiving out of memory. + * A value of zero will not limit allocations + * instructionlimit - maximum number of lua bytecode instructions before an error is thrown + * A value of zero will not limit the number of instructions + */ +lua_State *sb_newstate(uint64_t alloclimit, uint64_t instructionlimit); + +/* + * Replaces lua_close. Handles freeing the sb_lua_state + */ +void sb_close(lua_State *sb); + +/* + * Resets the instruction counter for the sandbox to 0 + */ +void sb_resetinstructioncounter(lua_State *sb); + +/* + * Sets the maximum number of lua instructions before erroring out + */ +void sb_setinstructionlimit(lua_State *L, uint64_t instruction_limit); + +/* + * Replaces luaL_openlibs. Only opens allowed paackages for the sandbox and + * masks out dangerous functions from the base. + */ +LUALIB_API void sb_loadrestricted(lua_State *L); + +#endif /* HAVE_LUA */ + +#endif /* __UTIL_LUA_SANDBOX_H__ */ \ No newline at end of file diff --git a/src/util-lua.c b/src/util-lua.c index d903f44e39..900e66401d 100644 --- a/src/util-lua.c +++ b/src/util-lua.c @@ -54,6 +54,7 @@ #include #include "util-lua.h" +#include "util-lua-sandbox.h" lua_State *LuaGetState(void) {