--- /dev/null
+/* 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 <pyrojoe314@gmail.com>
+ */
+
+#include "suricata-common.h"
+
+#ifdef HAVE_LUA
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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
--- /dev/null
+/* 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 <pyrojoe314@gmail.com>
+ */
+
+#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 <stdlib.h>
+#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