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  DEBUG_VALIDATE_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  if (ctx->alloc_limit != 0 && ctx->alloc_bytes + nsize > ctx->alloc_limit) {
68  /* This request will exceed the allocation limit. Act as
69  * though allocation failed. */
70  ctx->memory_limit_error = true;
71  return NULL;
72  }
73 
74  void *nptr = SCRealloc(ptr, nsize);
75  if (nptr != NULL) {
76  ctx->alloc_bytes += nsize;
77  ctx->alloc_bytes_allocated += nsize;
78  }
79  return nptr;
80  } else {
81  /* Resizing existing data. */
82  ssize_t diff = nsize - osize;
83 
84  if (ctx->alloc_limit != 0 && ctx->alloc_bytes + diff > ctx->alloc_limit) {
85  /* This request will exceed the allocation limit. Act as
86  * though allocation failed. */
87  ctx->memory_limit_error = true;
88  return NULL;
89  }
90 
91  void *nptr = SCRealloc(ptr, nsize);
92  if (nptr != NULL) {
93  DEBUG_VALIDATE_BUG_ON((ssize_t)ctx->alloc_bytes + diff < 0);
94  DEBUG_VALIDATE_BUG_ON(osize > ctx->alloc_bytes);
95  ctx->alloc_bytes += diff;
96  if (diff > 0) {
97  ctx->alloc_bytes_allocated += (size_t)diff;
98  }
99  }
100  return nptr;
101  }
102 }
103 
104 /**
105  * Function put in place of Lua functions that are blocked.
106  *
107  * TODO: Might want to create a version of this for each library that
108  * has blocked functions, so it can display the name of the
109  * library. As it doesn't appear that can be retrieved.
110  */
111 static int LuaBlockedFunction(lua_State *L)
112 {
113  SCLuaSbState *context = SCLuaSbGetContext(L);
114  context->blocked_function_error = true;
115  lua_Debug ar;
116  if (lua_getstack(L, 0, &ar) && lua_getinfo(L, "n", &ar) && ar.name) {
117  luaL_error(L, "Blocked Lua function called: %s", ar.name);
118  } else {
119  luaL_error(L, "Blocked Lua function: name not available");
120  }
121  /* never reached */
123  return -1;
124 }
125 
126 /**
127  * Check if a Lua function in a specific module is allowed.
128  *
129  * This is essentially an allow list for Lua functions.
130  */
131 static bool IsAllowed(const char *module, const char *fname)
132 {
133  static const char *base_allowed[] = {
134  "assert",
135  "ipairs",
136  "next",
137  "pairs",
138  "print",
139  "rawequal",
140  "rawlen",
141  "select",
142  "tonumber",
143  "tostring",
144  "type",
145  "warn",
146  "rawget",
147  "rawset",
148  "error",
149  NULL,
150  };
151 
152  /* Allow all. */
153  static const char *table_allowed[] = {
154  "concat",
155  "insert",
156  "move",
157  "pack",
158  "remove",
159  "sort",
160  "unpack",
161  NULL,
162  };
163 
164  /* Allow all. */
165  static const char *string_allowed[] = {
166  "byte",
167  "char",
168  "find",
169  "format",
170  "gmatch",
171  "gsub",
172  "len",
173  "lower",
174  "match",
175  "pack",
176  "packsize",
177  "rep",
178  "reverse",
179  "sub",
180  "unpack",
181  "upper",
182  NULL,
183  };
184 
185  /* Allow all. */
186  static const char *math_allowed[] = {
187  "abs",
188  "acos",
189  "asin",
190  "atan",
191  "atan2",
192  "ceil",
193  "cos",
194  "cosh",
195  "deg",
196  "exp",
197  "floor",
198  "fmod",
199  "frexp",
200  "ldexp",
201  "log",
202  "log10",
203  "max",
204  "min",
205  "modf",
206  "pow",
207  "rad",
208  "random",
209  "randomseed",
210  "sin",
211  "sinh",
212  "sqrt",
213  "tan",
214  "tanh",
215  "tointeger",
216  "type",
217  "ult",
218  NULL,
219  };
220 
221  /* Allow all. */
222  static const char *utf8_allowed[] = {
223  "offset",
224  "len",
225  "codes",
226  "char",
227  "codepoint",
228  NULL,
229  };
230 
231  const char **allowed = NULL;
232 
233  if (strcmp(module, LUA_GNAME) == 0) {
234  allowed = base_allowed;
235  } else if (strcmp(module, LUA_TABLIBNAME) == 0) {
236  allowed = table_allowed;
237  } else if (strcmp(module, LUA_STRLIBNAME) == 0) {
238  allowed = string_allowed;
239  } else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
240  allowed = math_allowed;
241  } else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
242  allowed = utf8_allowed;
243  } else {
244  /* This is a programming error. */
245  FatalError("Unknown Lua module %s", module);
246  }
247 
248  if (allowed) {
249  for (int i = 0; allowed[i] != NULL; i++) {
250  if (strcmp(allowed[i], fname) == 0) {
251  return true;
252  }
253  }
254  }
255 
256  return false;
257 }
258 
259 /**
260  * Set of libs that are allowed and loaded into the Lua state.
261  */
262 static const luaL_Reg AllowedLibs[] = {
263  // clang-format off
264  { LUA_GNAME, luaopen_base },
265  { LUA_TABLIBNAME, luaopen_table },
266  { LUA_STRLIBNAME, luaopen_string },
267  { LUA_MATHLIBNAME, luaopen_math },
268  { LUA_UTF8LIBNAME, luaopen_utf8 },
269  { NULL, NULL }
270  // clang-format on
271 };
272 
273 static int SCLuaSbRequire(lua_State *L)
274 {
275  const char *module_name = luaL_checkstring(L, 1);
276 
277  if (SCLuaLoadBuiltIns(L, module_name)) {
278  return 1;
279  }
280 
281  return luaL_error(L, "Module not found: %s", module_name);
282 }
283 
284 /**
285  * Load allowed Lua libraries into the state.
286  *
287  * Functions from each library that are not in the allowed list are
288  * replaced with LuaBlockedFunction.
289  */
291 {
292  const luaL_Reg *lib;
293 
294  /* the libs are the baseline, not script work: load uncapped so a
295  * max-bytes below the library footprint cannot abort a luaopen */
297  const uint64_t saved_limit = (sb != NULL) ? sb->alloc_limit : 0;
298  if (sb != NULL) {
299  sb->alloc_limit = 0;
300  }
301 
302  for (lib = AllowedLibs; lib->func; lib++) {
303  luaL_requiref(L, lib->name, lib->func, 1);
304  lua_pop(L, 1);
305  /* Iterate over all the functions in the just loaded table and
306  * replace functions now on the allow list with our blocked
307  * function placeholder. */
308  lua_getglobal(L, lib->name);
309  lua_pushnil(L);
310  while (lua_next(L, -2)) {
311  if (lua_type(L, -1) == LUA_TFUNCTION) {
312  const char *name = lua_tostring(L, -2);
313  if (!IsAllowed(lib->name, name)) {
314  SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
315  lua_pushstring(L, name);
316  lua_pushcfunction(L, LuaBlockedFunction);
317  lua_settable(L, -5);
318  } else {
319  SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
320  }
321  }
322  lua_pop(L, 1);
323  }
324  lua_pop(L, 1);
325  }
326 
327  /* Setup our custom require. */
328  lua_pushcfunction(L, SCLuaSbRequire);
329  lua_setglobal(L, "require");
330 
331  if (sb != NULL) {
332  sb->alloc_limit = saved_limit;
333  }
334 }
335 
336 /**
337  * \brief Allocate a new Lua sandbox.
338  *
339  * \returns An allocated sandbox state or NULL if memory allocation
340  * fails.
341  */
342 lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
343 {
344  SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
345  if (sb == NULL) {
346  return NULL;
347  }
348 
349  sb->alloc_limit = alloclimit;
350  sb->alloc_limit_cfg = alloclimit;
351  sb->alloc_bytes = 0;
352  sb->hook_instruction_count = 100;
353  sb->instruction_limit = instructionlimit;
354 
355  sb->L = lua_newstate(LuaAlloc, sb);
356  if (sb->L == NULL) {
357  SCFree(sb);
358  return NULL;
359  }
360 
361  lua_pushstring(sb->L, SANDBOX_CTX);
362  lua_pushlightuserdata(sb->L, sb);
363  lua_settable(sb->L, LUA_REGISTRYINDEX);
364 
365  lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
366  return sb->L;
367 }
368 
369 /**
370  * Get the Suricata Lua sandbox context from the lua_State.
371  *
372  * Note: May return null if this Lua state was not allocated from the
373  * sandbox.
374  */
376 {
377  lua_pushstring(L, SANDBOX_CTX);
378  lua_gettable(L, LUA_REGISTRYINDEX);
379  SCLuaSbState *ctx = lua_touserdata(L, -1);
380  lua_pop(L, 1);
381  return ctx;
382 }
383 
385 {
387  lua_close(sb->L);
388  BUG_ON(sb->alloc_bytes);
389  SCFree(sb);
390 }
391 
392 /**
393  * Lua debugging hook, but used here for instruction limit counting.
394  */
395 static void HookFunc(lua_State *L, lua_Debug *ar)
396 {
397  (void)ar;
399 
401 
402  if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
403  sb->instruction_count_error = true;
404  luaL_error(L, "instruction limit exceeded");
405  }
406 }
407 
409 {
410  uint64_t cfg_limit = 0;
412  if (sb != NULL) {
413  if (!sb->alloc_baseline_done) {
414  /* capture before the per-call preparation pushes, so the
415  * baseline is the steady setup footprint (libs, script,
416  * retained init state), not the first call's arguments:
417  * those are reachable and a compaction cannot drop them */
418  lua_gc(L, LUA_GCCOLLECT, 0);
419  sb->alloc_baseline = sb->alloc_bytes;
420  sb->alloc_baseline_done = true;
421  }
423  cfg_limit = sb->alloc_limit_cfg;
424  sb->alloc_limit = 0;
425  }
426  return cfg_limit;
427 }
428 
430 {
432  if (sb == NULL) {
433  return;
434  }
435  /* normally the baseline was captured by the Reset that opened the
436  * call; fall back here if a state is updated without a Reset */
437  if (!sb->alloc_baseline_done) {
438  lua_gc(L, LUA_GCCOLLECT, 0);
439  sb->alloc_baseline = sb->alloc_bytes;
440  sb->alloc_baseline_done = true;
441  }
442  /* a configured limit of 0 means unlimited: keep alloc_limit at 0 so the
443  * allocator skips the check (a cap equal to the baseline would fail any
444  * non-zero allocation) */
445  if (sb->alloc_limit_cfg == 0) {
446  sb->alloc_limit = 0;
447  } else {
448  /* fixed cap relative to the setup baseline. Re-basing on the live
449  * alloc_bytes per call (old behavior) let retained script memory
450  * escape the configured limit entirely */
451  /* the call's own inputs (allocated between the Reset and this
452  * Update) are exempt, as they were before the fixed cap: they are
453  * garbage once the call returns and retain nothing. The exemption
454  * is the gross allocation: a GC step during the setup pushes can
455  * reclaim the previous call's garbage, which the net delta would
456  * net against the inputs */
457  sb->alloc_limit = sb->alloc_baseline + sb->alloc_limit_cfg +
459  }
460 }
461 
462 void SCLuaSbRestoreBytesLimit(lua_State *L, const uint64_t cfg_limit)
463 {
465  if (sb != NULL) {
466  sb->alloc_limit = cfg_limit;
467  }
468 }
469 
470 /**
471  * Reset the instruction counter for the provided state.
472  */
474 {
476  if (sb != NULL) {
477  sb->blocked_function_error = false;
478  sb->instruction_count_error = false;
479  /* the flags are consumed by the per-call error classification,
480  * so they are as sticky as the other two */
481  sb->memory_limit_error = false;
482  sb->instruction_count = 0;
483  lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
484  }
485 }
SCLuaSbState::memory_limit_error
bool memory_limit_error
Definition: util-lua-sandbox.h:66
SCLuaSbUpdateBytesLimit
void SCLuaSbUpdateBytesLimit(lua_State *L)
Definition: util-lua-sandbox.c:429
SCLuaSbResetBytesLimit
uint64_t SCLuaSbResetBytesLimit(lua_State *L)
Definition: util-lua-sandbox.c:408
SCLuaSbState::alloc_baseline
uint64_t alloc_baseline
Definition: util-lua-sandbox.h:47
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
SCLuaSbGetContext
SCLuaSbState * SCLuaSbGetContext(lua_State *L)
Definition: util-lua-sandbox.c:375
name
const char * name
Definition: detect-engine-proto.c:47
SCLuaSbState
Definition: util-lua-sandbox.h:40
ctx
struct Thresholds ctx
SCLuaSbState::hook_instruction_count
int hook_instruction_count
Definition: util-lua-sandbox.h:61
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:527
util-debug.h
SCLuaSbRestoreBytesLimit
void SCLuaSbRestoreBytesLimit(lua_State *L, const uint64_t cfg_limit)
Definition: util-lua-sandbox.c:462
SCLuaSbState::alloc_baseline_done
bool alloc_baseline_done
Definition: util-lua-sandbox.h:48
SCLuaSbState::instruction_count
uint64_t instruction_count
Definition: util-lua-sandbox.h:58
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:322
SCLuaSbState::alloc_since_start
uint64_t alloc_since_start
Definition: util-lua-sandbox.h:53
SCLuaSbStateClose
void SCLuaSbStateClose(lua_State *L)
Definition: util-lua-sandbox.c:384
SCLuaSbState::blocked_function_error
bool blocked_function_error
Definition: util-lua-sandbox.h:64
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:72
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:290
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:59
SCLuaSbResetInstructionCounter
void SCLuaSbResetInstructionCounter(lua_State *L)
Definition: util-lua-sandbox.c:473
FatalError
#define FatalError(...)
Definition: util-debug.h:517
SCLuaSbState::alloc_bytes_allocated
uint64_t alloc_bytes_allocated
Definition: util-lua-sandbox.h:55
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:65
SCLuaSbStateNew
lua_State * SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
Allocate a new Lua sandbox.
Definition: util-lua-sandbox.c:342
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:109
SCLuaSbState::alloc_limit_cfg
uint64_t alloc_limit_cfg
Definition: util-lua-sandbox.h:46