lua: Add lua sandbox for detection rules

pull/11165/head
Jo Johnson 3 years ago committed by Jason Ish
parent 01c8af766c
commit 8428b0b9d7

@ -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 \

@ -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;
}

@ -27,6 +27,7 @@
#ifdef HAVE_LUA
#include "util-lua.h"
#include "util-lua-sandbox.h"
typedef struct DetectLuaThreadData {
lua_State *luastate;

@ -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 <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

@ -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 <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__ */

@ -54,6 +54,7 @@
#include <lauxlib.h>
#include "util-lua.h"
#include "util-lua-sandbox.h"
lua_State *LuaGetState(void)
{

Loading…
Cancel
Save