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