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