/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 | } |