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-validate.h"
33 #include "util-lua-sandbox.h"
34 #include "util-lua-dataset.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  lua_getstack(L, 0, &ar);
106  lua_getinfo(L, "n", &ar);
107  if (ar.name) {
108  luaL_error(L, "Blocked Lua function called: %s", ar.name);
109  } else {
110  luaL_error(L, "Blocked Lua function: name not available");
111  }
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 (strcmp(module_name, "suricata.dataset") == 0) {
269  return 1;
270  }
271 
272  return luaL_error(L, "Module not found: %s", module_name);
273 }
274 
275 /**
276  * Load allowed Lua libraries into the state.
277  *
278  * Functions from each library that are not in the allowed list are
279  * replaced with LuaBlockedFunction.
280  */
282 {
283  const luaL_Reg *lib;
284 
285  for (lib = AllowedLibs; lib->func; lib++) {
286  luaL_requiref(L, lib->name, lib->func, 1);
287  lua_pop(L, 1);
288  /* Iterate over all the functions in the just loaded table and
289  * replace functions now on the allow list with our blocked
290  * function placeholder. */
291  lua_getglobal(L, lib->name);
292  lua_pushnil(L);
293  while (lua_next(L, -2)) {
294  if (lua_type(L, -1) == LUA_TFUNCTION) {
295  const char *name = lua_tostring(L, -2);
296  if (!IsAllowed(lib->name, name)) {
297  SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
298  lua_pushstring(L, name);
299  lua_pushcfunction(L, LuaBlockedFunction);
300  lua_settable(L, -5);
301  } else {
302  SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
303  }
304  }
305  lua_pop(L, 1);
306  }
307  lua_pop(L, 1);
308  }
309 
310  /* Setup our custom require. */
311  lua_pushcfunction(L, SCLuaSbRequire);
312  lua_setglobal(L, "require");
313 }
314 
315 /**
316  * \brief Allocate a new Lua sandbox.
317  *
318  * \returns An allocated sandbox state or NULL if memory allocation
319  * fails.
320  */
321 lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
322 {
323  SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
324  if (sb == NULL) {
325  return NULL;
326  }
327 
328  sb->alloc_limit = alloclimit;
329  sb->alloc_bytes = 0;
330  sb->hook_instruction_count = 100;
331  sb->instruction_limit = instructionlimit;
332 
333  sb->L = lua_newstate(LuaAlloc, sb);
334  if (sb->L == NULL) {
335  SCFree(sb);
336  return NULL;
337  }
338 
339  lua_pushstring(sb->L, SANDBOX_CTX);
340  lua_pushlightuserdata(sb->L, sb);
341  lua_settable(sb->L, LUA_REGISTRYINDEX);
342 
343  lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
344  return sb->L;
345 }
346 
347 /**
348  * Get the Suricata Lua sandbox context from the lua_State.
349  *
350  * Note: May return null if this Lua state was not allocated from the
351  * sandbox.
352  */
354 {
355  lua_pushstring(L, SANDBOX_CTX);
356  lua_gettable(L, LUA_REGISTRYINDEX);
357  SCLuaSbState *ctx = lua_touserdata(L, -1);
358  lua_pop(L, 1);
359  return ctx;
360 }
361 
363 {
365  lua_close(sb->L);
366  BUG_ON(sb->alloc_bytes);
367  SCFree(sb);
368 }
369 
370 /**
371  * Lua debugging hook, but used here for instruction limit counting.
372  */
373 static void HookFunc(lua_State *L, lua_Debug *ar)
374 {
375  (void)ar;
377 
379 
380  if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
381  sb->instruction_count_error = true;
382  luaL_error(L, "instruction limit exceeded");
383  }
384 }
385 
386 /**
387  * Reset the instruction counter for the provided state.
388  */
390 {
392  if (sb != NULL) {
393  sb->blocked_function_error = false;
394  sb->instruction_count_error = false;
395  sb->instruction_count = 0;
396  lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
397  }
398 }
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:269
SCLuaSbGetContext
SCLuaSbState * SCLuaSbGetContext(lua_State *L)
Definition: util-lua-sandbox.c:353
SCLuaSbState
Definition: util-lua-sandbox.h:40
ctx
struct Thresholds ctx
SCLuaSbState::L
lua_State * L
Definition: util-lua-sandbox.h:41
util-lua-dataset.h
SCLuaSbState::hook_instruction_count
uint64_t hook_instruction_count
Definition: util-lua-sandbox.h:50
LuaLoadDatasetLib
void LuaLoadDatasetLib(lua_State *luastate)
Definition: util-lua-dataset.c:123
lua_State
struct lua_State lua_State
Definition: suricata-common.h:506
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:300
SCLuaSbStateClose
void SCLuaSbStateClose(lua_State *L)
Definition: util-lua-sandbox.c:362
name
const char * name
Definition: tm-threads.c:2081
SCLuaSbState::blocked_function_error
bool blocked_function_error
Definition: util-lua-sandbox.h:53
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:281
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:389
FatalError
#define FatalError(...)
Definition: util-debug.h:502
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:54
SCLuaSbStateNew
lua_State * SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
Allocate a new Lua sandbox.
Definition: util-lua-sandbox.c:321
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53