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