You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/util-lua-sandbox.c

398 lines
9.8 KiB
C

/* Copyright (C) 2023-2024 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"
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "util-debug.h"
#include "util-debug.h"
#include "util-lua-sandbox.h"
#include "util-lua-builtins.h"
#include "util-validate.h"
#define SANDBOX_CTX "SANDBOX_CTX"
static void HookFunc(lua_State *L, lua_Debug *ar);
/**
* Lua allocator function provided to lua_newstate.
*
* \param ud The pointer passed to lua_newstate
* \param ptr Pointer to data being allocated/reallocated/freed
* \param osize Original size of the block
* \param nsize Size of the new block
*
* See: https://www.lua.org/manual/5.4/manual.html#lua_Alloc
*/
static void *LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
(void)ud;
(void)osize;
SCLuaSbState *ctx = (SCLuaSbState *)ud;
if (nsize == 0) {
if (ptr == NULL) {
/* This happens, ignore. */
return NULL;
}
BUG_ON(osize > ctx->alloc_bytes);
SCFree(ptr);
ctx->alloc_bytes -= osize;
return NULL;
} else if (ptr == NULL) {
/* Allocating new data. */
void *nptr = SCRealloc(ptr, nsize);
if (nptr != NULL) {
ctx->alloc_bytes += nsize;
}
return nptr;
} else {
/* Resizing existing data. */
ssize_t diff = nsize - osize;
if (ctx->alloc_bytes + diff > ctx->alloc_limit) {
/* This request will exceed the allocation limit. Act as
* though allocation failed. */
ctx->memory_limit_error = true;
return NULL;
}
void *nptr = SCRealloc(ptr, nsize);
if (nptr != NULL) {
BUG_ON((ssize_t)ctx->alloc_bytes + diff < 0);
BUG_ON(osize > ctx->alloc_bytes);
ctx->alloc_bytes += diff;
}
return nptr;
}
}
/**
* Function put in place of Lua functions that are blocked.
*
* TODO: Might want to create a version of this for each library that
* has blocked functions, so it can display the name of the
* library. As it doesn't appear that can be retrieved.
*/
static int LuaBlockedFunction(lua_State *L)
{
SCLuaSbState *context = SCLuaSbGetContext(L);
context->blocked_function_error = true;
lua_Debug ar;
if (lua_getstack(L, 0, &ar) && lua_getinfo(L, "n", &ar) && ar.name) {
luaL_error(L, "Blocked Lua function called: %s", ar.name);
} else {
luaL_error(L, "Blocked Lua function: name not available");
}
/* never reached */
DEBUG_VALIDATE_BUG_ON(1);
return -1;
}
/**
* Check if a Lua function in a specific module is allowed.
*
* This is essentially an allow list for Lua functions.
*/
static bool IsAllowed(const char *module, const char *fname)
{
static const char *base_allowed[] = {
"assert",
"ipairs",
"next",
"pairs",
"print",
"rawequal",
"rawlen",
"select",
"tonumber",
"tostring",
"type",
"warn",
"rawget",
"rawset",
"error",
NULL,
};
/* Allow all. */
static const char *table_allowed[] = {
"concat",
"insert",
"move",
"pack",
"remove",
"sort",
"unpack",
NULL,
};
/* Allow all. */
static const char *string_allowed[] = {
"byte",
"char",
"dump",
"find",
"format",
"gmatch",
"gsub",
"len",
"lower",
"match",
"pack",
"packsize",
"rep",
"reverse",
"sub",
"unpack",
"upper",
NULL,
};
/* Allow all. */
static const char *math_allowed[] = {
"abs",
"acos",
"asin",
"atan",
"atan2",
"ceil",
"cos",
"cosh",
"deg",
"exp",
"floor",
"fmod",
"frexp",
"ldexp",
"log",
"log10",
"max",
"min",
"modf",
"pow",
"rad",
"random",
"randomseed",
"sin",
"sinh",
"sqrt",
"tan",
"tanh",
"tointeger",
"type",
"ult",
NULL,
};
/* Allow all. */
static const char *utf8_allowed[] = {
"offset",
"len",
"codes",
"char",
"codepoint",
NULL,
};
const char **allowed = NULL;
if (strcmp(module, LUA_GNAME) == 0) {
allowed = base_allowed;
} else if (strcmp(module, LUA_TABLIBNAME) == 0) {
allowed = table_allowed;
} else if (strcmp(module, LUA_STRLIBNAME) == 0) {
allowed = string_allowed;
} else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
allowed = math_allowed;
} else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
allowed = utf8_allowed;
} else {
/* This is a programming error. */
FatalError("Unknown Lua module %s", module);
}
if (allowed) {
for (int i = 0; allowed[i] != NULL; i++) {
if (strcmp(allowed[i], fname) == 0) {
return true;
}
}
}
return false;
}
/**
* Set of libs that are allowed and loaded into the Lua state.
*/
static const luaL_Reg AllowedLibs[] = {
// clang-format off
{ LUA_GNAME, luaopen_base },
{ LUA_TABLIBNAME, luaopen_table },
{ LUA_STRLIBNAME, luaopen_string },
{ LUA_MATHLIBNAME, luaopen_math },
{ LUA_UTF8LIBNAME, luaopen_utf8 },
{ NULL, NULL }
// clang-format on
};
static int SCLuaSbRequire(lua_State *L)
{
const char *module_name = luaL_checkstring(L, 1);
if (SCLuaLoadBuiltIns(L, module_name)) {
return 1;
}
return luaL_error(L, "Module not found: %s", module_name);
}
/**
* Load allowed Lua libraries into the state.
*
* Functions from each library that are not in the allowed list are
* replaced with LuaBlockedFunction.
*/
void SCLuaSbLoadLibs(lua_State *L)
{
const luaL_Reg *lib;
for (lib = AllowedLibs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1);
/* Iterate over all the functions in the just loaded table and
* replace functions now on the allow list with our blocked
* function placeholder. */
lua_getglobal(L, lib->name);
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_type(L, -1) == LUA_TFUNCTION) {
const char *name = lua_tostring(L, -2);
if (!IsAllowed(lib->name, name)) {
SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
lua_pushstring(L, name);
lua_pushcfunction(L, LuaBlockedFunction);
lua_settable(L, -5);
} else {
SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
/* Setup our custom require. */
lua_pushcfunction(L, SCLuaSbRequire);
lua_setglobal(L, "require");
}
/**
* \brief Allocate a new Lua sandbox.
*
* \returns An allocated sandbox state or NULL if memory allocation
* fails.
*/
lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
{
SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
if (sb == NULL) {
return NULL;
}
sb->alloc_limit = alloclimit;
sb->alloc_bytes = 0;
sb->hook_instruction_count = 100;
sb->instruction_limit = instructionlimit;
sb->L = lua_newstate(LuaAlloc, sb);
if (sb->L == NULL) {
SCFree(sb);
return NULL;
}
lua_pushstring(sb->L, SANDBOX_CTX);
lua_pushlightuserdata(sb->L, sb);
lua_settable(sb->L, LUA_REGISTRYINDEX);
lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
return sb->L;
}
/**
* Get the Suricata Lua sandbox context from the lua_State.
*
* Note: May return null if this Lua state was not allocated from the
* sandbox.
*/
SCLuaSbState *SCLuaSbGetContext(lua_State *L)
{
lua_pushstring(L, SANDBOX_CTX);
lua_gettable(L, LUA_REGISTRYINDEX);
SCLuaSbState *ctx = lua_touserdata(L, -1);
lua_pop(L, 1);
return ctx;
}
void SCLuaSbStateClose(lua_State *L)
{
SCLuaSbState *sb = SCLuaSbGetContext(L);
lua_close(sb->L);
BUG_ON(sb->alloc_bytes);
SCFree(sb);
}
/**
* Lua debugging hook, but used here for instruction limit counting.
*/
static void HookFunc(lua_State *L, lua_Debug *ar)
{
(void)ar;
SCLuaSbState *sb = SCLuaSbGetContext(L);
sb->instruction_count += sb->hook_instruction_count;
if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
sb->instruction_count_error = true;
luaL_error(L, "instruction limit exceeded");
}
}
/**
* Reset the instruction counter for the provided state.
*/
void SCLuaSbResetInstructionCounter(lua_State *L)
{
SCLuaSbState *sb = SCLuaSbGetContext(L);
if (sb != NULL) {
sb->blocked_function_error = false;
sb->instruction_count_error = false;
sb->instruction_count = 0;
lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
}
}