• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23374838686

21 Mar 2026 07:29AM UTC coverage: 59.341% (-20.0%) from 79.315%
23374838686

Pull #15075

github

web-flow
Merge 90b4e834f into 6587e363a
Pull Request #15075: Stack 8001 v16.4

38 of 70 new or added lines in 10 files covered. (54.29%)

34165 existing lines in 563 files now uncovered.

119621 of 201584 relevant lines covered (59.34%)

650666.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.34
/src/util-lua-sandbox.c
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"
15,888✔
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
{
3,066,898✔
52
    (void)ud;
3,066,898✔
53
    (void)osize;
3,066,898✔
54
    SCLuaSbState *ctx = (SCLuaSbState *)ud;
3,066,898✔
55

56
    if (nsize == 0) {
3,066,898✔
57
        if (ptr == NULL) {
1,604,961✔
58
            /* This happens, ignore. */
59
            return NULL;
174,804✔
60
        }
174,804✔
61
        DEBUG_VALIDATE_BUG_ON(osize > ctx->alloc_bytes);
1,430,157✔
62
        SCFree(ptr);
1,430,157✔
63
        ctx->alloc_bytes -= osize;
1,430,157✔
64
        return NULL;
1,430,157✔
65
    } else if (ptr == NULL) {
1,604,961✔
66
        /* Allocating new data. */
67
        void *nptr = SCRealloc(ptr, nsize);
1,430,157✔
68
        if (nptr != NULL) {
1,430,157✔
69
            ctx->alloc_bytes += nsize;
1,430,157✔
70
        }
1,430,157✔
71
        return nptr;
1,430,157✔
72
    } else {
1,430,157✔
73
        /* Resizing existing data. */
74
        ssize_t diff = nsize - osize;
31,780✔
75

76
        if (ctx->alloc_limit != 0 && ctx->alloc_bytes + diff > ctx->alloc_limit) {
31,780✔
77
            /* This request will exceed the allocation limit. Act as
78
             * though allocation failed. */
UNCOV
79
            ctx->memory_limit_error = true;
×
UNCOV
80
            return NULL;
×
UNCOV
81
        }
×
82

83
        void *nptr = SCRealloc(ptr, nsize);
31,780✔
84
        if (nptr != NULL) {
31,780✔
85
            DEBUG_VALIDATE_BUG_ON((ssize_t)ctx->alloc_bytes + diff < 0);
31,780✔
86
            DEBUG_VALIDATE_BUG_ON(osize > ctx->alloc_bytes);
31,780✔
87
            ctx->alloc_bytes += diff;
31,780✔
88
        }
31,780✔
89
        return nptr;
31,780✔
90
    }
31,780✔
91
}
3,066,898✔
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)
UNCOV
101
{
×
UNCOV
102
    SCLuaSbState *context = SCLuaSbGetContext(L);
×
UNCOV
103
    context->blocked_function_error = true;
×
UNCOV
104
    lua_Debug ar;
×
UNCOV
105
    if (lua_getstack(L, 0, &ar) && lua_getinfo(L, "n", &ar) && ar.name) {
×
UNCOV
106
        luaL_error(L, "Blocked Lua function called: %s", ar.name);
×
UNCOV
107
    } else {
×
108
        luaL_error(L, "Blocked Lua function: name not available");
×
109
    }
×
110
    /* never reached */
UNCOV
111
    DEBUG_VALIDATE_BUG_ON(1);
×
UNCOV
112
    return -1;
×
UNCOV
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
{
659,352✔
122
    static const char *base_allowed[] = {
659,352✔
123
        "assert",
659,352✔
124
        "ipairs",
659,352✔
125
        "next",
659,352✔
126
        "pairs",
659,352✔
127
        "print",
659,352✔
128
        "rawequal",
659,352✔
129
        "rawlen",
659,352✔
130
        "select",
659,352✔
131
        "tonumber",
659,352✔
132
        "tostring",
659,352✔
133
        "type",
659,352✔
134
        "warn",
659,352✔
135
        "rawget",
659,352✔
136
        "rawset",
659,352✔
137
        "error",
659,352✔
138
        NULL,
659,352✔
139
    };
659,352✔
140

141
    /* Allow all. */
142
    static const char *table_allowed[] = {
659,352✔
143
        "concat",
659,352✔
144
        "insert",
659,352✔
145
        "move",
659,352✔
146
        "pack",
659,352✔
147
        "remove",
659,352✔
148
        "sort",
659,352✔
149
        "unpack",
659,352✔
150
        NULL,
659,352✔
151
    };
659,352✔
152

153
    /* Allow all. */
154
    static const char *string_allowed[] = {
659,352✔
155
        "byte",
659,352✔
156
        "char",
659,352✔
157
        "dump",
659,352✔
158
        "find",
659,352✔
159
        "format",
659,352✔
160
        "gmatch",
659,352✔
161
        "gsub",
659,352✔
162
        "len",
659,352✔
163
        "lower",
659,352✔
164
        "match",
659,352✔
165
        "pack",
659,352✔
166
        "packsize",
659,352✔
167
        "rep",
659,352✔
168
        "reverse",
659,352✔
169
        "sub",
659,352✔
170
        "unpack",
659,352✔
171
        "upper",
659,352✔
172
        NULL,
659,352✔
173
    };
659,352✔
174

175
    /* Allow all. */
176
    static const char *math_allowed[] = {
659,352✔
177
        "abs",
659,352✔
178
        "acos",
659,352✔
179
        "asin",
659,352✔
180
        "atan",
659,352✔
181
        "atan2",
659,352✔
182
        "ceil",
659,352✔
183
        "cos",
659,352✔
184
        "cosh",
659,352✔
185
        "deg",
659,352✔
186
        "exp",
659,352✔
187
        "floor",
659,352✔
188
        "fmod",
659,352✔
189
        "frexp",
659,352✔
190
        "ldexp",
659,352✔
191
        "log",
659,352✔
192
        "log10",
659,352✔
193
        "max",
659,352✔
194
        "min",
659,352✔
195
        "modf",
659,352✔
196
        "pow",
659,352✔
197
        "rad",
659,352✔
198
        "random",
659,352✔
199
        "randomseed",
659,352✔
200
        "sin",
659,352✔
201
        "sinh",
659,352✔
202
        "sqrt",
659,352✔
203
        "tan",
659,352✔
204
        "tanh",
659,352✔
205
        "tointeger",
659,352✔
206
        "type",
659,352✔
207
        "ult",
659,352✔
208
        NULL,
659,352✔
209
    };
659,352✔
210

211
    /* Allow all. */
212
    static const char *utf8_allowed[] = {
659,352✔
213
        "offset",
659,352✔
214
        "len",
659,352✔
215
        "codes",
659,352✔
216
        "char",
659,352✔
217
        "codepoint",
659,352✔
218
        NULL,
659,352✔
219
    };
659,352✔
220

221
    const char **allowed = NULL;
659,352✔
222

223
    if (strcmp(module, LUA_GNAME) == 0) {
659,352✔
224
        allowed = base_allowed;
182,712✔
225
    } else if (strcmp(module, LUA_TABLIBNAME) == 0) {
476,640✔
226
        allowed = table_allowed;
55,608✔
227
    } else if (strcmp(module, LUA_STRLIBNAME) == 0) {
421,032✔
228
        allowed = string_allowed;
135,048✔
229
    } else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
285,984✔
230
        allowed = math_allowed;
246,264✔
231
    } else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
246,264✔
232
        allowed = utf8_allowed;
39,720✔
233
    } else {
39,720✔
234
        /* This is a programming error. */
235
        FatalError("Unknown Lua module %s", module);
×
236
    }
×
237

238
    if (allowed) {
659,352✔
239
        for (int i = 0; allowed[i] != NULL; i++) {
7,467,360✔
240
            if (strcmp(allowed[i], fname) == 0) {
7,403,808✔
241
                return true;
595,800✔
242
            }
595,800✔
243
        }
7,403,808✔
244
    }
659,352✔
245

246
    return false;
63,552✔
247
}
659,352✔
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)
UNCOV
264
{
×
UNCOV
265
    const char *module_name = luaL_checkstring(L, 1);
×
266

UNCOV
267
    if (SCLuaLoadBuiltIns(L, module_name)) {
×
UNCOV
268
        return 1;
×
UNCOV
269
    }
×
270

271
    return luaL_error(L, "Module not found: %s", module_name);
×
UNCOV
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
 */
280
void SCLuaSbLoadLibs(lua_State *L)
281
{
7,944✔
282
    const luaL_Reg *lib;
7,944✔
283

284
    for (lib = AllowedLibs; lib->func; lib++) {
47,664✔
285
        luaL_requiref(L, lib->name, lib->func, 1);
39,720✔
286
        lua_pop(L, 1);
39,720✔
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);
39,720✔
291
        lua_pushnil(L);
39,720✔
292
        while (lua_next(L, -2)) {
754,680✔
293
            if (lua_type(L, -1) == LUA_TFUNCTION) {
714,960✔
294
                const char *name = lua_tostring(L, -2);
659,352✔
295
                if (!IsAllowed(lib->name, name)) {
659,352✔
296
                    SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
63,552✔
297
                    lua_pushstring(L, name);
63,552✔
298
                    lua_pushcfunction(L, LuaBlockedFunction);
63,552✔
299
                    lua_settable(L, -5);
63,552✔
300
                } else {
595,800✔
301
                    SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
595,800✔
302
                }
595,800✔
303
            }
659,352✔
304
            lua_pop(L, 1);
714,960✔
305
        }
714,960✔
306
        lua_pop(L, 1);
39,720✔
307
    }
39,720✔
308

309
    /* Setup our custom require. */
310
    lua_pushcfunction(L, SCLuaSbRequire);
7,944✔
311
    lua_setglobal(L, "require");
7,944✔
312
}
7,944✔
313

314
/**
315
 * \brief Allocate a new Lua sandbox.
316
 *
317
 * \returns An allocated sandbox state or NULL if memory allocation
318
 *     fails.
319
 */
320
lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
321
{
7,944✔
322
    SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
7,944✔
323
    if (sb == NULL) {
7,944✔
324
        return NULL;
×
325
    }
×
326

327
    sb->alloc_limit = alloclimit;
7,944✔
328
    sb->alloc_bytes = 0;
7,944✔
329
    sb->hook_instruction_count = 100;
7,944✔
330
    sb->instruction_limit = instructionlimit;
7,944✔
331

332
    sb->L = lua_newstate(LuaAlloc, sb);
7,944✔
333
    if (sb->L == NULL) {
7,944✔
334
        SCFree(sb);
×
335
        return NULL;
×
336
    }
×
337

338
    lua_pushstring(sb->L, SANDBOX_CTX);
7,944✔
339
    lua_pushlightuserdata(sb->L, sb);
7,944✔
340
    lua_settable(sb->L, LUA_REGISTRYINDEX);
7,944✔
341

342
    lua_sethook(sb->L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
7,944✔
343
    return sb->L;
7,944✔
344
}
7,944✔
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
 */
352
SCLuaSbState *SCLuaSbGetContext(lua_State *L)
353
{
7,944✔
354
    lua_pushstring(L, SANDBOX_CTX);
7,944✔
355
    lua_gettable(L, LUA_REGISTRYINDEX);
7,944✔
356
    SCLuaSbState *ctx = lua_touserdata(L, -1);
7,944✔
357
    lua_pop(L, 1);
7,944✔
358
    return ctx;
7,944✔
359
}
7,944✔
360

361
void SCLuaSbStateClose(lua_State *L)
362
{
7,944✔
363
    SCLuaSbState *sb = SCLuaSbGetContext(L);
7,944✔
364
    lua_close(sb->L);
7,944✔
365
    BUG_ON(sb->alloc_bytes);
7,944✔
366
    SCFree(sb);
7,944✔
367
}
7,944✔
368

369
/**
370
 * Lua debugging hook, but used here for instruction limit counting.
371
 */
372
static void HookFunc(lua_State *L, lua_Debug *ar)
UNCOV
373
{
×
UNCOV
374
    (void)ar;
×
UNCOV
375
    SCLuaSbState *sb = SCLuaSbGetContext(L);
×
376

UNCOV
377
    sb->instruction_count += sb->hook_instruction_count;
×
378

UNCOV
379
    if (sb->instruction_limit > 0 && sb->instruction_count > sb->instruction_limit) {
×
UNCOV
380
        sb->instruction_count_error = true;
×
UNCOV
381
        luaL_error(L, "instruction limit exceeded");
×
UNCOV
382
    }
×
UNCOV
383
}
×
384

385
uint64_t SCLuaSbResetBytesLimit(lua_State *L)
UNCOV
386
{
×
UNCOV
387
    uint64_t cfg_limit = 0;
×
UNCOV
388
    SCLuaSbState *sb = SCLuaSbGetContext(L);
×
UNCOV
389
    if (sb != NULL) {
×
UNCOV
390
        cfg_limit = sb->alloc_limit;
×
UNCOV
391
        sb->alloc_limit = 0;
×
UNCOV
392
    }
×
UNCOV
393
    return cfg_limit;
×
UNCOV
394
}
×
395

396
void SCLuaSbUpdateBytesLimit(lua_State *L)
UNCOV
397
{
×
UNCOV
398
    SCLuaSbState *sb = SCLuaSbGetContext(L);
×
UNCOV
399
    if (sb != NULL) {
×
UNCOV
400
        sb->alloc_limit = sb->alloc_bytes + sb->alloc_limit;
×
UNCOV
401
    }
×
UNCOV
402
}
×
403

404
void SCLuaSbRestoreBytesLimit(lua_State *L, const uint64_t cfg_limit)
UNCOV
405
{
×
UNCOV
406
    SCLuaSbState *sb = SCLuaSbGetContext(L);
×
UNCOV
407
    if (sb != NULL) {
×
UNCOV
408
        sb->alloc_limit = cfg_limit;
×
UNCOV
409
    }
×
UNCOV
410
}
×
411

412
/**
413
 * Reset the instruction counter for the provided state.
414
 */
415
void SCLuaSbResetInstructionCounter(lua_State *L)
UNCOV
416
{
×
UNCOV
417
    SCLuaSbState *sb = SCLuaSbGetContext(L);
×
UNCOV
418
    if (sb != NULL) {
×
UNCOV
419
        sb->blocked_function_error = false;
×
UNCOV
420
        sb->instruction_count_error = false;
×
UNCOV
421
        sb->instruction_count = 0;
×
UNCOV
422
        lua_sethook(L, HookFunc, LUA_MASKCOUNT, sb->hook_instruction_count);
×
UNCOV
423
    }
×
UNCOV
424
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc