Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/src/detect-transform-luaxform.c
Line
Count
Source
1
/* Copyright (C) 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 Jeff Lucovsky <jlucovsky@oisf.net>
22
 *
23
 * Implements the luxaform transform keyword
24
 */
25
26
#include "suricata-common.h"
27
28
#include "detect.h"
29
#include "detect-engine.h"
30
#include "detect-engine-buffer.h"
31
#include "detect-parse.h"
32
#include "detect-lua.h"
33
#include "detect-transform-luaxform.h"
34
#include "detect-lua-extensions.h"
35
36
#include "util-lua.h"
37
#include "util-lua-common.h"
38
#include "util-lua-builtins.h"
39
40
static int DetectTransformLuaxformSetup(DetectEngineCtx *, Signature *, const char *);
41
static void DetectTransformLuaxformFree(DetectEngineCtx *de_ctx, void *ptr);
42
static void TransformLuaxform(
43
        DetectEngineThreadCtx *det_ctx, InspectionBuffer *buffer, const void *options);
44
45
58
#define LUAXFORM_MAX_ARGS 10
46
47
typedef struct DetectLuaxformData {
48
    int thread_ctx_id;
49
    int allow_restricted_functions;
50
    int arg_count;
51
    uint64_t alloc_limit;
52
    uint64_t instruction_limit;
53
    const char *filename;
54
    char *copystr;
55
    const char *id_data;
56
    uint32_t id_data_len;
57
    const char *args[LUAXFORM_MAX_ARGS];
58
} DetectLuaxformData;
59
60
typedef struct DetectLuaxformThreadData {
61
    lua_State *luastate;
62
} DetectLuaxformThreadData;
63
64
static void DetectTransformLuaxformId(const uint8_t **data, uint32_t *length, const void *context)
65
0
{
66
0
    if (context) {
67
0
        DetectLuaxformData *lua = (DetectLuaxformData *)context;
68
0
        *data = (uint8_t *)lua->id_data;
69
0
        *length = lua->id_data_len;
70
0
    }
71
0
}
72
73
static void DetectTransformLuaxformFree(DetectEngineCtx *de_ctx, void *ptr)
74
34
{
75
34
    if (ptr != NULL) {
76
34
        DetectLuaxformData *lua = (DetectLuaxformData *)ptr;
77
78
34
        if (lua->filename)
79
34
            SCFree((void *)lua->filename);
80
81
34
        if (lua->copystr)
82
34
            SCFree((void *)lua->copystr);
83
84
34
        if (lua->id_data)
85
34
            SCFree((void *)lua->id_data);
86
87
34
        if (de_ctx) {
88
34
            DetectUnregisterThreadCtxFuncs(de_ctx, lua, "luaxform");
89
34
        }
90
91
34
        SCFree(lua);
92
34
    }
93
34
}
94
95
static int DetectTransformLuaxformSetupPrime(
96
        DetectEngineCtx *de_ctx, DetectLuaxformData *ld, const Signature *s)
97
34
{
98
34
    lua_State *luastate = SCLuaSbStateNew(ld->alloc_limit, ld->instruction_limit);
99
34
    if (luastate == NULL)
100
0
        return -1;
101
34
    if (ld->allow_restricted_functions) {
102
0
        luaL_openlibs(luastate);
103
0
        SCLuaRequirefBuiltIns(luastate);
104
34
    } else {
105
34
        SCLuaSbLoadLibs(luastate);
106
34
    }
107
108
34
    int status = luaL_loadfile(luastate, ld->filename);
109
34
    if (status) {
110
34
        SCLogError("couldn't load file: %s", lua_tostring(luastate, -1));
111
34
        goto error;
112
34
    }
113
114
    /* prime the script (or something) */
115
0
    if (lua_pcall(luastate, 0, 0, 0) != 0) {
116
0
        SCLogError("couldn't prime file: %s", lua_tostring(luastate, -1));
117
0
        goto error;
118
0
    }
119
120
0
    lua_getglobal(luastate, "transform");
121
0
    if (lua_type(luastate, -1) != LUA_TFUNCTION) {
122
0
        SCLogError("no transform function in script");
123
0
        goto error;
124
0
    }
125
0
    lua_pop(luastate, 1);
126
127
0
    SCLuaSbStateClose(luastate);
128
0
    return 0;
129
130
34
error:
131
34
    SCLuaSbStateClose(luastate);
132
34
    return -1;
133
0
}
134
135
static DetectLuaxformData *DetectLuaxformParse(DetectEngineCtx *de_ctx, const char *optsstr)
136
34
{
137
34
    DetectLuaxformData *lua = NULL;
138
139
    /* We have a correct lua option */
140
34
    lua = SCCalloc(1, sizeof(DetectLuaxformData));
141
34
    if (unlikely(lua == NULL)) {
142
0
        FatalError("unable to allocate memory for Lua transform: %s", optsstr);
143
0
    }
144
145
34
    lua->copystr = strdup(optsstr);
146
34
    lua->id_data = strdup(optsstr);
147
34
    if (unlikely(lua->copystr == NULL || lua->id_data == NULL)) {
148
0
        FatalError("unable to allocate memory for Lua transform: %s", optsstr);
149
0
    }
150
151
34
    lua->id_data_len = (uint32_t)strlen(lua->id_data);
152
153
34
    int count = 0;
154
34
    char *saveptr = NULL;
155
34
    char *token = strtok_r(lua->copystr, ",", &saveptr);
156
92
    while (token != NULL && count < LUAXFORM_MAX_ARGS) {
157
58
        lua->args[count++] = token;
158
58
        token = strtok_r(NULL, ",", &saveptr);
159
58
    }
160
161
34
    if (count == 0) {
162
0
        SCLogError("Lua script name not supplied");
163
0
        goto error;
164
0
    }
165
166
34
    lua->arg_count = count - 1;
167
168
    /* get full filename */
169
34
    lua->filename = DetectLoadCompleteSigPath(de_ctx, lua->args[0]);
170
34
    if (lua->filename == NULL) {
171
0
        goto error;
172
0
    }
173
174
34
    return lua;
175
176
0
error:
177
0
    if (lua != NULL)
178
0
        DetectTransformLuaxformFree(de_ctx, lua);
179
0
    return NULL;
180
34
}
181
182
static void *DetectLuaxformThreadInit(void *data)
183
0
{
184
    /* Note: This will always be non-null as alloc errors are checked before registering callback */
185
0
    DetectLuaxformData *lua = (DetectLuaxformData *)data;
186
187
0
    DetectLuaThreadData *t = SCCalloc(1, sizeof(DetectLuaThreadData));
188
0
    if (unlikely(t == NULL)) {
189
0
        FatalError("unable to allocate luaxform context memory");
190
0
    }
191
192
0
    t->luastate = SCLuaSbStateNew(lua->alloc_limit, lua->instruction_limit);
193
0
    if (t->luastate == NULL) {
194
0
        SCLogError("luastate pool depleted");
195
0
        goto error;
196
0
    }
197
198
0
    if (lua->allow_restricted_functions) {
199
0
        luaL_openlibs(t->luastate);
200
0
        SCLuaRequirefBuiltIns(t->luastate);
201
0
    } else {
202
0
        SCLuaSbLoadLibs(t->luastate);
203
0
    }
204
205
0
    int status = luaL_loadfile(t->luastate, lua->filename);
206
0
    if (status) {
207
0
        SCLogError("couldn't load file: %s", lua_tostring(t->luastate, -1));
208
0
        goto error;
209
0
    }
210
211
    /* prime the script (or something) */
212
0
    if (lua_pcall(t->luastate, 0, 0, 0) != 0) {
213
0
        SCLogError("couldn't prime file: %s", lua_tostring(t->luastate, -1));
214
0
        goto error;
215
0
    }
216
217
    /* when present: thread_init call */
218
0
    lua_getglobal(t->luastate, "thread_init");
219
0
    if (lua_isfunction(t->luastate, -1)) {
220
0
        if (lua_pcall(t->luastate, 0, 0, 0) != 0) {
221
0
            SCLogError("couldn't run script 'thread_init' function: %s",
222
0
                    lua_tostring(t->luastate, -1));
223
0
            goto error;
224
0
        }
225
0
    } else {
226
0
        lua_pop(t->luastate, 1);
227
0
    }
228
229
0
    return (void *)t;
230
231
0
error:
232
0
    if (t->luastate != NULL)
233
0
        SCLuaSbStateClose(t->luastate);
234
0
    SCFree(t);
235
0
    return NULL;
236
0
}
237
238
static void DetectLuaxformThreadFree(void *ctx)
239
0
{
240
0
    if (ctx != NULL) {
241
0
        DetectLuaxformThreadData *t = (DetectLuaxformThreadData *)ctx;
242
0
        if (t->luastate != NULL)
243
0
            SCLuaSbStateClose(t->luastate);
244
0
        SCFree(t);
245
0
    }
246
0
}
247
248
/**
249
 *  \internal
250
 *  \brief Apply the luaxform keyword to the last pattern match
251
 *  \param de_ctx detection engine ctx
252
 *  \param s signature
253
 *  \param str lua filename and optional args
254
 *  \retval 0 ok
255
 *  \retval -1 failure
256
 */
257
static int DetectTransformLuaxformSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optsstr)
258
34
{
259
34
    SCEnter();
260
261
    /* First check if Lua rules are enabled, by default Lua in rules
262
     * is disabled. */
263
34
    int enabled = 0;
264
34
    if (SCConfGetBool("security.lua.allow-rules", &enabled) == 1 && !enabled) {
265
0
        SCLogError("Lua rules disabled by security configuration: security.lua.allow-rules");
266
0
        SCReturnInt(-1);
267
0
    }
268
269
34
    DetectLuaxformData *lua = DetectLuaxformParse(de_ctx, optsstr);
270
34
    if (lua == NULL)
271
0
        goto error;
272
273
    /* Load lua sandbox configurations */
274
34
    intmax_t lua_alloc_limit = DEFAULT_LUA_ALLOC_LIMIT;
275
34
    intmax_t lua_instruction_limit = DEFAULT_LUA_INSTRUCTION_LIMIT;
276
34
    int allow_restricted_functions = 0;
277
34
    (void)SCConfGetInt("security.lua.max-bytes", &lua_alloc_limit);
278
34
    (void)SCConfGetInt("security.lua.max-instructions", &lua_instruction_limit);
279
34
    (void)SCConfGetBool("security.lua.allow-restricted-functions", &allow_restricted_functions);
280
281
34
    lua->alloc_limit = lua_alloc_limit;
282
34
    lua->instruction_limit = lua_instruction_limit;
283
34
    lua->allow_restricted_functions = allow_restricted_functions;
284
285
34
    if (DetectTransformLuaxformSetupPrime(de_ctx, lua, s) == -1) {
286
34
        goto error;
287
34
    }
288
289
0
    lua->thread_ctx_id = DetectRegisterThreadCtxFuncs(
290
0
            de_ctx, "luaxform", DetectLuaxformThreadInit, (void *)lua, DetectLuaxformThreadFree, 0);
291
0
    if (lua->thread_ctx_id == -1)
292
0
        goto error;
293
294
0
    if (0 == SCDetectSignatureAddTransform(s, DETECT_TRANSFORM_LUAXFORM, lua))
295
0
        SCReturnInt(0);
296
297
34
error:
298
299
34
    if (lua != NULL)
300
34
        DetectTransformLuaxformFree(de_ctx, lua);
301
34
    SCReturnInt(-1);
302
0
}
303
304
static void TransformLuaxform(
305
        DetectEngineThreadCtx *det_ctx, InspectionBuffer *buffer, const void *options)
306
0
{
307
0
    if (buffer->inspect_len == 0) {
308
0
        return;
309
0
    }
310
311
0
    const DetectLuaxformData *lua = options;
312
0
    DetectLuaThreadData *tlua =
313
0
            (DetectLuaThreadData *)DetectThreadCtxGetKeywordThreadCtx(det_ctx, lua->thread_ctx_id);
314
0
    if (tlua == NULL) {
315
0
        return;
316
0
    }
317
318
0
    lua_getglobal(tlua->luastate, "transform");
319
320
0
    const uint8_t *input = buffer->inspect;
321
0
    const uint32_t input_len = buffer->inspect_len;
322
323
    /* disable bytes limit temporarily to allow the setup of buffer and other data the script will
324
     * use. */
325
0
    const uint64_t cfg_limit = SCLuaSbResetBytesLimit(tlua->luastate);
326
327
    /* Lua script args are: buffer, rule args table */
328
0
    LuaPushStringBuffer(tlua->luastate, input, (size_t)input_len);
329
330
    /*
331
     * Add provided arguments for lua script (these are optionally
332
     * provided by the rule writer).
333
     *
334
     * Start at offset 1 (arg[0] is the lua script filename)
335
     */
336
0
    lua_newtable(tlua->luastate);
337
0
    for (int i = 1; i < lua->arg_count + 1; i++) {
338
0
        LuaPushInteger(tlua->luastate, i);
339
0
        lua_pushstring(tlua->luastate, lua->args[i]);
340
0
        lua_settable(tlua->luastate, -3);
341
0
    }
342
343
    /* restore configured bytes limit and account for the allocations done for the setup above. */
344
0
    SCLuaSbRestoreBytesLimit(tlua->luastate, cfg_limit);
345
0
    SCLuaSbUpdateBytesLimit(tlua->luastate);
346
0
    SCLuaSbResetInstructionCounter(tlua->luastate);
347
348
0
    if (LUA_OK != lua_pcall(tlua->luastate, 2, 2, 0)) {
349
0
        SCLogDebug("error calling lua script: %s", lua_tostring(tlua->luastate, -1));
350
0
    } else {
351
        /* Lua transform functions must return 2 values: buffer and length */
352
0
        int return_value_count = lua_gettop(tlua->luastate);
353
0
        if (return_value_count != 2) {
354
0
            SCLogDebug("Error: expected 2 return values but got %d", return_value_count);
355
0
            goto error;
356
0
        }
357
358
0
        if (lua_isstring(tlua->luastate, -2)) {
359
0
            const char *transformed_buffer = lua_tostring(tlua->luastate, -2);
360
0
            lua_Integer transformed_buffer_byte_count = lua_tointeger(tlua->luastate, -1);
361
0
            if (transformed_buffer != NULL && transformed_buffer_byte_count > 0)
362
0
                InspectionBufferCopy(buffer, (uint8_t *)transformed_buffer,
363
0
                        (uint32_t)transformed_buffer_byte_count);
364
0
            SCLogDebug("transform returns [nbytes %d] \"%p\"",
365
0
                    (uint32_t)transformed_buffer_byte_count, transformed_buffer);
366
0
        }
367
0
    }
368
369
0
error:
370
0
    while (lua_gettop(tlua->luastate) > 0) {
371
0
        lua_pop(tlua->luastate, 1);
372
0
    }
373
0
    SCLuaSbRestoreBytesLimit(tlua->luastate, cfg_limit);
374
0
}
375
376
void DetectTransformLuaxformRegister(void)
377
41
{
378
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].name = "luaxform";
379
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].desc =
380
41
            "pass inspection buffer to a Lua function along with "
381
41
            "arguments supplied to the transform";
382
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].url = "/rules/transforms.html#luaxform";
383
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].Transform = TransformLuaxform;
384
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].Free = DetectTransformLuaxformFree;
385
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].Setup = DetectTransformLuaxformSetup;
386
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].flags |= SIGMATCH_QUOTES_OPTIONAL;
387
41
    sigmatch_table[DETECT_TRANSFORM_LUAXFORM].TransformId = DetectTransformLuaxformId;
388
41
}