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  }
78  return nptr;
79  } else {
80  /* Resizing existing data. */
81  ssize_t diff = nsize - osize;
82 
83  if (ctx->alloc_limit != 0 && ctx->alloc_bytes + diff > ctx->alloc_limit) {
84  /* This request will exceed the allocation limit. Act as
85  * though allocation failed. */
86  ctx->memory_limit_error = true;
87  return NULL;
88  }
89 
90  void *nptr = SCRealloc(ptr, nsize);
91  if (nptr != NULL) {
92  DEBUG_VALIDATE_BUG_ON((ssize_t)ctx->alloc_bytes + diff < 0);
93  DEBUG_VALIDATE_BUG_ON(osize > ctx->alloc_bytes);
94  ctx->alloc_bytes += diff;
95  }
96  return nptr;
97  }
98 }
99 
100 /**
101  * Function put in place of Lua functions that are blocked.
102  *
103  * TODO: Might want to create a version of this for each library that
104  * has blocked functions, so it can display the name of the
105  * library. As it doesn't appear that can be retrieved.
106  */
107 static int LuaBlockedFunction(lua_State *L)
108 {
109  SCLuaSbState *context = SCLuaSbGetContext(L);
110  context->blocked_function_error = true;
111  lua_Debug ar;
112  if (lua_getstack(L, 0, &ar) && lua_getinfo(L, "n", &ar) && ar.name) {
113  luaL_error(L, "Blocked Lua function called: %s", ar.name);
114  } else {
115  luaL_error(L, "Blocked Lua function: name not available");
116  }
117  /* never reached */
119  return -1;
120 }
121 
122 /**
123  * Check if a Lua function in a specific module is allowed.
124  *
125  * This is essentially an allow list for Lua functions.
126  */
127 static bool IsAllowed(const char *module, const char *fname)
128 {
129  static const char *base_allowed[] = {
130  "assert",
131  "ipairs",
132  "next",
133  "pairs",
134  "print",
135  "rawequal",
136  "rawlen",
137  "select",
138  "tonumber",
139  "tostring",
140  "type",
141  "warn",
142  "rawget",
143  "rawset",
144  "error",
145  NULL,
146  };
147 
148  /* Allow all. */
149  static const char *table_allowed[] = {
150  "concat",
151  "insert",
152  "move",
153  "pack",
154  "remove",
155  "sort",
156  "unpack",
157  NULL,
158  };
159 
160  /* Allow all. */
161  static const char *string_allowed[] = {
162  "byte",
163  "char",
164  "dump",
165  "find",
166  "format",
167  "gmatch",
168  "gsub",
169  "len",
170  "lower",
171  "match",
172  "pack",
173  "packsize",
174  "rep",
175  "reverse",
176  "sub",
177  "unpack",
178  "upper",
179  NULL,
180  };
181 
182  /* Allow all. */
183  static const char *math_allowed[] = {
184  "abs",
185  "acos",
186  "asin",
187  "atan",
188  "atan2",
189  "ceil",
190  "cos",
191  "cosh",
192  "deg",
193  "exp",
194  "floor",
195  "fmod",
196  "frexp",
197  "ldexp",
198  "log",
199  "log10",
200  "max",
201  "min",
202  "modf",
203  "pow",
204  "rad",
205  "random",
206  "randomseed",
207  "sin",
208  "sinh",
209  "sqrt",
210  "tan",
211  "tanh",
212  "tointeger",
213  "type",
214  "ult",
215  NULL,
216  };
217 
218  /* Allow all. */
219  static const char *utf8_allowed[] = {
220  "offset",
221  "len",
222  "codes",
223  "char",
224  "codepoint",
225  NULL,
226  };
227 
228  const char **allowed = NULL;
229 
230  if (strcmp(module, LUA_GNAME) == 0) {
231  allowed = base_allowed;
232  } else if (strcmp(module, LUA_TABLIBNAME) == 0) {
233  allowed = table_allowed;
234  } else if (strcmp(module, LUA_STRLIBNAME) == 0) {
235  allowed = string_allowed;
236  } else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
237  allowed = math_allowed;
238  } else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
239  allowed = utf8_allowed;
240  } else {
241  /* This is a programming error. */
242  FatalError("Unknown Lua module %s", module);
243  }
244 
245  if (allowed) {
246  for (int i = 0; allowed[i] != NULL; i++) {
247  if (strcmp(allowed[i], fname) == 0) {
248  return true;
249  }
250  }
251  }
252 
253  return false;
254 }
255 
256 /**
257  * Set of libs that are allowed and loaded into the Lua state.
258  */
259 static const luaL_Reg AllowedLibs[] = {
260  // clang-format off
261  { LUA_GNAME, luaopen_base },
262  { LUA_TABLIBNAME, luaopen_table },
263  { LUA_STRLIBNAME, luaopen_string },
264  { LUA_MATHLIBNAME, luaopen_math },
265  { LUA_UTF8LIBNAME, luaopen_utf8 },
266  { NULL, NULL }
267  // clang-format on
268 };
269 
270 static int SCLuaSbRequire(lua_State *L)
271 {
272  const char *module_name = luaL_checkstring(L, 1);
273 
274  if (SCLuaLoadBuiltIns(L, module_name)) {
275  return 1;
276  }
277 
278  return luaL_error(L, "Module not found: %s", module_name);
279 }
280 
281 /**
282  * Load allowed Lua libraries into the state.
283  *
284  * Functions from each library that are not in the allowed list are
285  * replaced with LuaBlockedFunction.
286  */
288 {
289  const luaL_Reg *lib;
290 
291  for (lib = AllowedLibs; lib->func; lib++) {
292  luaL_requiref(L, lib->name, lib->func, 1);
293  lua_pop(L, 1);
294  /* Iterate over all the functions in the just loaded table and
295  * replace functions now on the allow list with our blocked
296  * function placeholder. */
297  lua_getglobal(L, lib->name);
298  lua_pushnil(L);
299  while (lua_next(L, -2)) {
300  if (lua_type(L, -1) == LUA_TFUNCTION) {
301  const char *name = lua_tostring(L, -2);
302  if (!IsAllowed(lib->name, name)) {
303  SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
304  lua_pushstring(L, name);
305  lua_pushcfunction(L, LuaBlockedFunction);
306  lua_settable(L, -5);
307  } else {
308  SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
309  }
310  }
311  lua_pop(L, 1);
312  }
313  lua_pop(L, 1);
314  }
315 
316  /* Setup our custom require. */
317  lua_pushcfunction(L, SCLuaSbRequire);
318  lua_setglobal(L, "require");
319 }
320 
321 /**
322  * \brief Allocate a new Lua sandbox.
323  *
324  * \returns An allocated sandbox state or NULL if memory allocation
325  * fails.
326  */
327 lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
328 {
329  SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
330  if (sb == NULL) {
331  return NULL;
332  }
333 
334  sb->alloc_limit = alloclimit;
335  sb->alloc_bytes = 0;
336  sb->hook_instruction_count = 100;
337  sb->instruction_limit = instructionlimit;
338 
339  sb->L = lua_newstate(LuaAlloc, sb);
340  if (sb->L == NULL) {
341  SCFree(sb);
342  return NULL;
343  }
344 
345  lua_pushstring(sb->L, SANDBOX_CTX);
346  lua_pushlightuserdata(sb->L, sb);
347  lua_settable(sb->L, LUA_REGISTRYINDEX);
348 
349  lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
350  return sb->L;
351 }
352 
353 /**
354  * Get the Suricata Lua sandbox context from the lua_State.
355  *
356  * Note: May return null if this Lua state was not allocated from the
357  * sandbox.
358  */
360 {
361  lua_pushstring(L, SANDBOX_CTX);
362  lua_gettable(L, LUA_REGISTRYINDEX);
363  SCLuaSbState *ctx = lua_touserdata(L, -1);
364  lua_pop(L, 1);
365  return ctx;
366 }
367 
369 {
371  lua_close(sb->L);
372  BUG_ON(sb->alloc_bytes);
373  SCFree(sb);
374 }
375 
376 /**
377  * Lua debugging hook, but used here for instruction limit counting.
378  */
379 static void HookFunc(lua_State *L, lua_Debug *ar)
380 {
381  (void)ar;
383 
385 
386  if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
387  sb->instruction_count_error = true;
388  luaL_error(L, "instruction limit exceeded");
389  }
390 }
391 
393 {
394  uint64_t cfg_limit = 0;
396  if (sb != NULL) {
397  cfg_limit = sb->alloc_limit;
398  sb->alloc_limit = 0;
399  }
400  return cfg_limit;
401 }
402 
404 {
406  if (sb != NULL) {
407  sb->alloc_limit = sb->alloc_bytes + sb->alloc_limit;
408  }
409 }
410 
411 void SCLuaSbRestoreBytesLimit(lua_State *L, const uint64_t cfg_limit)
412 {
414  if (sb != NULL) {
415  sb->alloc_limit = cfg_limit;
416  }
417 }
418 
419 /**
420  * Reset the instruction counter for the provided state.
421  */
423 {
425  if (sb != NULL) {
426  sb->blocked_function_error = false;
427  sb->instruction_count_error = false;
428  sb->instruction_count = 0;
429  lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
430  }
431 }
SCLuaSbUpdateBytesLimit
void SCLuaSbUpdateBytesLimit(lua_State *L)
Definition: util-lua-sandbox.c:403
SCLuaSbResetBytesLimit
uint64_t SCLuaSbResetBytesLimit(lua_State *L)
Definition: util-lua-sandbox.c:392
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
SCLuaSbGetContext
SCLuaSbState * SCLuaSbGetContext(lua_State *L)
Definition: util-lua-sandbox.c:359
name
const char * name
Definition: detect-engine-proto.c:48
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:530
util-debug.h
SCLuaSbRestoreBytesLimit
void SCLuaSbRestoreBytesLimit(lua_State *L, const uint64_t cfg_limit)
Definition: util-lua-sandbox.c:411
SCLuaSbState::instruction_count
uint64_t instruction_count
Definition: util-lua-sandbox.h:48
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:325
SCLuaSbStateClose
void SCLuaSbStateClose(lua_State *L)
Definition: util-lua-sandbox.c:368
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: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:287
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:422
FatalError
#define FatalError(...)
Definition: util-debug.h:517
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:327
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