Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add lua sandbox files
Browse files Browse the repository at this point in the history
J0eJ0h committed Jan 26, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent ff0438e commit eb56d54
Showing 6 changed files with 411 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
@@ -572,6 +572,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 \
@@ -1175,6 +1176,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 \
18 changes: 11 additions & 7 deletions src/detect-lua.c
Original file line number Diff line number Diff line change
@@ -90,6 +90,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 *);
@@ -104,6 +105,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
*/
@@ -603,7 +607,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;
@@ -649,7 +653,7 @@ static void *DetectLuaThreadInit(void *data)

error:
if (t->luastate != NULL)
LuaReturnState(t->luastate);
sb_close(t->luastate);
SCFree(t);
return NULL;
}
@@ -659,7 +663,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);
}
}
@@ -705,10 +709,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
@@ -987,10 +991,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;
}

1 change: 1 addition & 0 deletions src/detect-lua.h
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
#ifdef HAVE_LUA

#include "util-lua.h"
#include "util-lua-sandbox.h"

typedef struct DetectLuaThreadData {
lua_State *luastate;
298 changes: 298 additions & 0 deletions src/util-lua-sandbox.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/* 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 <[email protected]>
*/

#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;
}
free(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 = realloc(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));
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 == NULL) {
// Out of memory. Error code?
return NULL;
}
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
98 changes: 98 additions & 0 deletions src/util-lua-sandbox.h
Original file line number Diff line number Diff line change
@@ -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__ */
1 change: 1 addition & 0 deletions src/util-lua.c
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@
#include <lauxlib.h>

#include "util-lua.h"
#include "util-lua-sandbox.h"

lua_State *LuaGetState(void)
{

0 comments on commit eb56d54

Please sign in to comment.