suricata
util-lua-sandbox.c
Go to the documentation of this file.
1 /* Copyright (C) 2023-2024 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Jo Johnson <pyrojoe314@gmail.com>
22  */
23 
24 #include "suricata-common.h"
25 
26 #include "lua.h"
27 #include "lauxlib.h"
28 #include "lualib.h"
29 #include "util-debug.h"
30 
31 #include "util-debug.h"
32 #include "util-lua-sandbox.h"
33 #include "util-lua-builtins.h"
34 #include "util-validate.h"
35 
36 #define SANDBOX_CTX "SANDBOX_CTX"
37 
38 static void HookFunc(lua_State *L, lua_Debug *ar);
39 
40 /**
41  * Lua allocator function provided to lua_newstate.
42  *
43  * \param ud The pointer passed to lua_newstate
44  * \param ptr Pointer to data being allocated/reallocated/freed
45  * \param osize Original size of the block
46  * \param nsize Size of the new block
47  *
48  * See: https://www.lua.org/manual/5.4/manual.html#lua_Alloc
49  */
50 static void *LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
51 {
52  (void)ud;
53  (void)osize;
54  SCLuaSbState *ctx = (SCLuaSbState *)ud;
55 
56  if (nsize == 0) {
57  if (ptr == NULL) {
58  /* This happens, ignore. */
59  return NULL;
60  }
61  BUG_ON(osize > ctx->alloc_bytes);
62  SCFree(ptr);
63  ctx->alloc_bytes -= osize;
64  return NULL;
65  } else if (ptr == NULL) {
66  /* Allocating new data. */
67  void *nptr = SCRealloc(ptr, nsize);
68  if (nptr != NULL) {
69  ctx->alloc_bytes += nsize;
70  }
71  return nptr;
72  } else {
73  /* Resizing existing data. */
74  ssize_t diff = nsize - osize;
75 
76  if (ctx->alloc_bytes + diff > ctx->alloc_limit) {
77  /* This request will exceed the allocation limit. Act as
78  * though allocation failed. */
79  ctx->memory_limit_error = true;
80  return NULL;
81  }
82 
83  void *nptr = SCRealloc(ptr, nsize);
84  if (nptr != NULL) {
85  BUG_ON((ssize_t)ctx->alloc_bytes + diff < 0);
86  BUG_ON(osize > ctx->alloc_bytes);
87  ctx->alloc_bytes += diff;
88  }
89  return nptr;
90  }
91 }
92 
93 /**
94  * Function put in place of Lua functions that are blocked.
95  *
96  * TODO: Might want to create a version of this for each library that
97  * has blocked functions, so it can display the name of the
98  * library. As it doesn't appear that can be retrieved.
99  */
100 static int LuaBlockedFunction(lua_State *L)
101 {
102  SCLuaSbState *context = SCLuaSbGetContext(L);
103  context->blocked_function_error = true;
104  lua_Debug ar;
105  if (lua_getstack(L, 0, &ar) && lua_getinfo(L, "n", &ar) && ar.name) {
106  luaL_error(L, "Blocked Lua function called: %s", ar.name);
107  } else {
108  luaL_error(L, "Blocked Lua function: name not available");
109  }
110  /* never reached */
112  return -1;
113 }
114 
115 /**
116  * Check if a Lua function in a specific module is allowed.
117  *
118  * This is essentially an allow list for Lua functions.
119  */
120 static bool IsAllowed(const char *module, const char *fname)
121 {
122  static const char *base_allowed[] = {
123  "assert",
124  "ipairs",
125  "next",
126  "pairs",
127  "print",
128  "rawequal",
129  "rawlen",
130  "select",
131  "tonumber",
132  "tostring",
133  "type",
134  "warn",
135  "rawget",
136  "rawset",
137  "error",
138  NULL,
139  };
140 
141  /* Allow all. */
142  static const char *table_allowed[] = {
143  "concat",
144  "insert",
145  "move",
146  "pack",
147  "remove",
148  "sort",
149  "unpack",
150  NULL,
151  };
152 
153  /* Allow all. */
154  static const char *string_allowed[] = {
155  "byte",
156  "char",
157  "dump",
158  "find",
159  "format",
160  "gmatch",
161  "gsub",
162  "len",
163  "lower",
164  "match",
165  "pack",
166  "packsize",
167  "rep",
168  "reverse",
169  "sub",
170  "unpack",
171  "upper",
172  NULL,
173  };
174 
175  /* Allow all. */
176  static const char *math_allowed[] = {
177  "abs",
178  "acos",
179  "asin",
180  "atan",
181  "atan2",
182  "ceil",
183  "cos",
184  "cosh",
185  "deg",
186  "exp",
187  "floor",
188  "fmod",
189  "frexp",
190  "ldexp",
191  "log",
192  "log10",
193  "max",
194  "min",
195  "modf",
196  "pow",
197  "rad",
198  "random",
199  "randomseed",
200  "sin",
201  "sinh",
202  "sqrt",
203  "tan",
204  "tanh",
205  "tointeger",
206  "type",
207  "ult",
208  NULL,
209  };
210 
211  /* Allow all. */
212  static const char *utf8_allowed[] = {
213  "offset",
214  "len",
215  "codes",
216  "char",
217  "codepoint",
218  NULL,
219  };
220 
221  const char **allowed = NULL;
222 
223  if (strcmp(module, LUA_GNAME) == 0) {
224  allowed = base_allowed;
225  } else if (strcmp(module, LUA_TABLIBNAME) == 0) {
226  allowed = table_allowed;
227  } else if (strcmp(module, LUA_STRLIBNAME) == 0) {
228  allowed = string_allowed;
229  } else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
230  allowed = math_allowed;
231  } else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
232  allowed = utf8_allowed;
233  } else {
234  /* This is a programming error. */
235  FatalError("Unknown Lua module %s", module);
236  }
237 
238  if (allowed) {
239  for (int i = 0; allowed[i] != NULL; i++) {
240  if (strcmp(allowed[i], fname) == 0) {
241  return true;
242  }
243  }
244  }
245 
246  return false;
247 }
248 
249 /**
250  * Set of libs that are allowed and loaded into the Lua state.
251  */
252 static const luaL_Reg AllowedLibs[] = {
253  // clang-format off
254  { LUA_GNAME, luaopen_base },
255  { LUA_TABLIBNAME, luaopen_table },
256  { LUA_STRLIBNAME, luaopen_string },
257  { LUA_MATHLIBNAME, luaopen_math },
258  { LUA_UTF8LIBNAME, luaopen_utf8 },
259  { NULL, NULL }
260  // clang-format on
261 };
262 
263 static int SCLuaSbRequire(lua_State *L)
264 {
265  const char *module_name = luaL_checkstring(L, 1);
266 
267  if (SCLuaLoadBuiltIns(L, module_name)) {
268  return 1;
269  }
270 
271  return luaL_error(L, "Module not found: %s", module_name);
272 }
273 
274 /**
275  * Load allowed Lua libraries into the state.
276  *
277  * Functions from each library that are not in the allowed list are
278  * replaced with LuaBlockedFunction.
279  */
281 {
282  const luaL_Reg *lib;
283 
284  for (lib = AllowedLibs; lib->func; lib++) {
285  luaL_requiref(L, lib->name, lib->func, 1);
286  lua_pop(L, 1);
287  /* Iterate over all the functions in the just loaded table and
288  * replace functions now on the allow list with our blocked
289  * function placeholder. */
290  lua_getglobal(L, lib->name);
291  lua_pushnil(L);
292  while (lua_next(L, -2)) {
293  if (lua_type(L, -1) == LUA_TFUNCTION) {
294  const char *name = lua_tostring(L, -2);
295  if (!IsAllowed(lib->name, name)) {
296  SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
297  lua_pushstring(L, name);
298  lua_pushcfunction(L, LuaBlockedFunction);
299  lua_settable(L, -5);
300  } else {
301  SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
302  }
303  }
304  lua_pop(L, 1);
305  }
306  lua_pop(L, 1);
307  }
308 
309  /* Setup our custom require. */
310  lua_pushcfunction(L, SCLuaSbRequire);
311  lua_setglobal(L, "require");
312 }
313 
314 /**
315  * \brief Allocate a new Lua sandbox.
316  *
317  * \returns An allocated sandbox state or NULL if memory allocation
318  * fails.
319  */
320 lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
321 {
322  SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
323  if (sb == NULL) {
324  return NULL;
325  }
326 
327  sb->alloc_limit = alloclimit;
328  sb->alloc_bytes = 0;
329  sb->hook_instruction_count = 100;
330  sb->instruction_limit = instructionlimit;
331 
332  sb->L = lua_newstate(LuaAlloc, sb);
333  if (sb->L == NULL) {
334  SCFree(sb);
335  return NULL;
336  }
337 
338  lua_pushstring(sb->L, SANDBOX_CTX);
339  lua_pushlightuserdata(sb->L, sb);
340  lua_settable(sb->L, LUA_REGISTRYINDEX);
341 
342  lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
343  return sb->L;
344 }
345 
346 /**
347  * Get the Suricata Lua sandbox context from the lua_State.
348  *
349  * Note: May return null if this Lua state was not allocated from the
350  * sandbox.
351  */
353 {
354  lua_pushstring(L, SANDBOX_CTX);
355  lua_gettable(L, LUA_REGISTRYINDEX);
356  SCLuaSbState *ctx = lua_touserdata(L, -1);
357  lua_pop(L, 1);
358  return ctx;
359 }
360 
362 {
364  lua_close(sb->L);
365  BUG_ON(sb->alloc_bytes);
366  SCFree(sb);
367 }
368 
369 /**
370  * Lua debugging hook, but used here for instruction limit counting.
371  */
372 static void HookFunc(lua_State *L, lua_Debug *ar)
373 {
374  (void)ar;
376 
378 
379  if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
380  sb->instruction_count_error = true;
381  luaL_error(L, "instruction limit exceeded");
382  }
383 }
384 
385 /**
386  * Reset the instruction counter for the provided state.
387  */
389 {
391  if (sb != NULL) {
392  sb->blocked_function_error = false;
393  sb->instruction_count_error = false;
394  sb->instruction_count = 0;
395  lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
396  }
397 }
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:275
SCLuaSbGetContext
SCLuaSbState * SCLuaSbGetContext(lua_State *L)
Definition: util-lua-sandbox.c:352
SCLuaSbState
Definition: util-lua-sandbox.h:40
ctx
struct Thresholds ctx
SCLuaSbState::hook_instruction_count
int hook_instruction_count
Definition: util-lua-sandbox.h:51
SCLuaSbState::L
lua_State * L
Definition: util-lua-sandbox.h:41
util-lua-builtins.h
lua_State
struct lua_State lua_State
Definition: suricata-common.h:523
util-debug.h
SCLuaSbState::instruction_count
uint64_t instruction_count
Definition: util-lua-sandbox.h:48
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:317
SCLuaSbStateClose
void SCLuaSbStateClose(lua_State *L)
Definition: util-lua-sandbox.c:361
name
const char * name
Definition: tm-threads.c:2163
SCLuaSbState::blocked_function_error
bool blocked_function_error
Definition: util-lua-sandbox.h:54
SCLuaLoadBuiltIns
bool SCLuaLoadBuiltIns(lua_State *L, const char *name)
Load a Suricata built-in module in a sand-boxed environment.
Definition: util-lua-builtins.c:70
SCRealloc
#define SCRealloc(ptr, sz)
Definition: util-mem.h:50
util-lua-sandbox.h
SCLuaSbLoadLibs
void SCLuaSbLoadLibs(lua_State *L)
Definition: util-lua-sandbox.c:280
suricata-common.h
SANDBOX_CTX
#define SANDBOX_CTX
Definition: util-lua-sandbox.c:36
SCLuaSbState::instruction_limit
uint64_t instruction_limit
Definition: util-lua-sandbox.h:49
SCLuaSbResetInstructionCounter
void SCLuaSbResetInstructionCounter(lua_State *L)
Definition: util-lua-sandbox.c:388
FatalError
#define FatalError(...)
Definition: util-debug.h:510
util-validate.h
SCFree
#define SCFree(p)
Definition: util-mem.h:61
SCLuaSbState::alloc_bytes
size_t alloc_bytes
Definition: util-lua-sandbox.h:44
SCLuaSbState::alloc_limit
uint64_t alloc_limit
Definition: util-lua-sandbox.h:45
SCLuaSbState::instruction_count_error
bool instruction_count_error
Definition: util-lua-sandbox.h:55
SCLuaSbStateNew
lua_State * SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
Allocate a new Lua sandbox.
Definition: util-lua-sandbox.c:320
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:102