From 11b3ebcb5f60b36eefc8cb397590b30a8b71a958 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Mon, 2 Jun 2025 14:37:57 -0600 Subject: [PATCH] lua/bytevar: convert SCByteVar to Lua lib Similar to flowvars and flowints, but a byte var cannot be registered from a Lua script, but it still needs to be setup. Instead provide an "map" function that sets it up, or errors out if the byte var is unknown. This also required passing the signature into the Lua init method, as the state of the Signature object and the time of loading the Lua keyword is required. --- doc/userguide/lua/libs/bytevar.rst | 80 ++++++++++++++++++ doc/userguide/lua/libs/index.rst | 1 + doc/userguide/lua/lua-functions.rst | 25 ------ src/Makefile.am | 2 + src/detect-lua-extensions.c | 46 ---------- src/detect-lua.c | 37 ++------ src/detect-lua.h | 7 +- src/util-lua-builtins.c | 2 + src/util-lua-bytevarlib.c | 127 ++++++++++++++++++++++++++++ src/util-lua-bytevarlib.h | 25 ++++++ 10 files changed, 251 insertions(+), 101 deletions(-) create mode 100644 doc/userguide/lua/libs/bytevar.rst create mode 100644 src/util-lua-bytevarlib.c create mode 100644 src/util-lua-bytevarlib.h diff --git a/doc/userguide/lua/libs/bytevar.rst b/doc/userguide/lua/libs/bytevar.rst new file mode 100644 index 0000000000..0b9221a09e --- /dev/null +++ b/doc/userguide/lua/libs/bytevar.rst @@ -0,0 +1,80 @@ +Bytevar +####### + +The ``suricata.bytevar`` module provides access to variables defined by +``byte_extract`` and ``byte_math`` keywords in Suricata rules. + +It is only available in Suricata Lua rules, not output scripts. + +Setup +***** + +:: + + local bytevars = require("suricata.bytevar") + +Module Functions +**************** + +.. function:: bytevars.map(sig, varname) + + Ensures that the ``bytevar`` exists and sets it up for further use + in the script by mapping it into the Lua context. Must be called + during ``init()``. + + :param sig: The signature object passed to ``init()`` + :param string varname: Name of the variable as defined in the rule + + :raises error: If the variable name is unknown + :raises error: If too many byte variables are mapped + + Example: + + :: + + function init(sig) + bytevars.map(sig, "var1") + bytevars.map(sig, "var2") + return {} + end + +.. function:: bytevars.get(name) + + Returns a byte variable object for the given name. May be called + during ``thread_init()`` to save a handle to the bytevar. + + :param number name: Name of the variable previously setup with + ``map()``. + + :raises error: If variable name is not mapped with ``map()``. + + :returns: A byte variable object + + Example: + + :: + + function thread_init() + bv_var1 = bytevars.get("var1") + bv_var2 = bytevars.get("var2") + end + +Byte Variable Object Methods +**************************** + +.. method:: bytevar:value() + + Returns the current value of the byte variable. + + :returns: The value of the byte variable. + + Example: + + :: + + function match(args) + local var1 = bv_var1:value() + if var1 then + -- Use the value + end + end diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index cc63fb99c8..188ca1784f 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -9,6 +9,7 @@ environment without access to additional modules. .. toctree:: base64 + bytevar config dns file diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst index 8a23957e41..9f87197670 100644 --- a/doc/userguide/lua/lua-functions.rst +++ b/doc/userguide/lua/lua-functions.rst @@ -313,28 +313,3 @@ SCThreadInfo tid, tname, tgroup = SCThreadInfo() It gives: tid (integer), tname (string), tgroup (string) - - - -SCByteVarGet -~~~~~~~~~~~~ - -Get the ByteVar at index given by the parameter. These variables are defined by -`byte_extract` or `byte_math` in Suricata rules. Only callable from match scripts. - -:: - - function init(args) - local needs = {} - needs["bytevar"] = {"var1", "var2"} - return needs - end - -Here we define a register that we will be using variables `var1` and `var2`. -The access to the Byte variables is done by index. - -:: - - function match(args) - var1 = SCByteVarGet(0) - var2 = SCByteVarGet(1) diff --git a/src/Makefile.am b/src/Makefile.am index 44ba243ff9..9dde1185b6 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -545,6 +545,7 @@ noinst_HEADERS = \ util-lua-flowintlib.h \ util-lua-flowlib.h \ util-lua-flowvarlib.h \ + util-lua-bytevarlib.h \ util-lua-hashlib.h \ util-lua-http.h \ util-lua-ja3.h \ @@ -1124,6 +1125,7 @@ libsuricata_c_a_SOURCES = \ util-lua-flowintlib.c \ util-lua-flowlib.c \ util-lua-flowvarlib.c \ + util-lua-bytevarlib.c \ util-lua-hashlib.c \ util-lua-http.c \ util-lua-ja3.c \ diff --git a/src/detect-lua-extensions.c b/src/detect-lua-extensions.c index ea415404d8..e8c810d0b6 100644 --- a/src/detect-lua-extensions.c +++ b/src/detect-lua-extensions.c @@ -38,54 +38,11 @@ #include "util-lua.h" #include "util-lua-common.h" -#include "util-lua-smtp.h" -#include "util-lua-dnp3.h" #include "detect-lua-extensions.h" /* Lua registry key for DetectLuaData. */ const char luaext_key_ld[] = "suricata:luadata"; -static int GetLuaData(lua_State *luastate, DetectLuaData **ret_ld) -{ - *ret_ld = NULL; - - DetectLuaData *ld; - lua_pushlightuserdata(luastate, (void *)&luaext_key_ld); - lua_gettable(luastate, LUA_REGISTRYINDEX); - ld = lua_touserdata(luastate, -1); - if (ld == NULL) { - LUA_ERROR("internal error: no ld"); - } - *ret_ld = ld; - return 0; -} - -static int LuaGetByteVar(lua_State *luastate) -{ - DetectLuaData *ld = NULL; - DetectEngineThreadCtx *det_ctx = LuaStateGetDetCtx(luastate); - - if (det_ctx == NULL) - return LuaCallbackError(luastate, "internal error: no ldet_ctx"); - - int ret = GetLuaData(luastate, &ld); - if (ret != 0) - return ret; - - if (!lua_isnumber(luastate, 1)) { - LUA_ERROR("bytevar id not a number"); - } - int id = lua_tonumber(luastate, 1); - if (id < 0 || id >= DETECT_LUA_MAX_BYTEVARS) { - LUA_ERROR("bytevar id out of range"); - } - uint32_t idx = ld->bytevar[id]; - - lua_pushinteger(luastate, det_ctx->byte_values[idx]); - - return 1; -} - void LuaExtensionsMatchSetup(lua_State *lua_state, DetectLuaData *ld, DetectEngineThreadCtx *det_ctx, Flow *f, Packet *p, const Signature *s, uint8_t flags) { @@ -115,9 +72,6 @@ void LuaExtensionsMatchSetup(lua_State *lua_state, DetectLuaData *ld, */ int LuaRegisterExtensions(lua_State *lua_state) { - lua_pushcfunction(lua_state, LuaGetByteVar); - lua_setglobal(lua_state, "SCByteVarGet"); - LuaRegisterFunctions(lua_state); return 0; } diff --git a/src/detect-lua.c b/src/detect-lua.c index ac2b6dc3f3..92e1e80eec 100644 --- a/src/detect-lua.c +++ b/src/detect-lua.c @@ -566,7 +566,11 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const goto error; } - if (lua_pcall(luastate, 0, 1, 0) != 0) { + /* Pass the signature as the first argument, setting up bytevars depends on + * access to the signature. */ + lua_pushlightuserdata(luastate, (void *)s); + + if (lua_pcall(luastate, 1, 1, 0) != 0) { SCLogError("couldn't run script 'init' function: %s", lua_tostring(luastate, -1)); goto error; } @@ -633,34 +637,6 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const } lua_pop(luastate, 1); continue; - } else if (strcmp(k, "bytevar") == 0) { - if (lua_istable(luastate, -1)) { - lua_pushnil(luastate); - while (lua_next(luastate, -2) != 0) { - /* value at -1, key is at -2 which we ignore */ - const char *value = lua_tostring(luastate, -1); - SCLogDebug("value %s", value); - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(luastate, 1); - - if (ld->bytevars == DETECT_LUA_MAX_BYTEVARS) { - SCLogError("too many bytevars registered"); - goto error; - } - - DetectByteIndexType idx; - if (!DetectByteRetrieveSMVar(value, s, &idx)) { - SCLogError("Unknown byte_extract or byte_math var " - "requested by lua script - %s", - value); - goto error; - } - ld->bytevar[ld->bytevars++] = idx; - SCLogDebug("script uses bytevar %u with script id %u", idx, ld->bytevars - 1); - } - } - lua_pop(luastate, 1); - continue; } bool required = lua_toboolean(luastate, -1); @@ -821,6 +797,9 @@ static void DetectLuaFree(DetectEngineCtx *de_ctx, void *ptr) for (uint16_t i = 0; i < lua->flowvars; i++) { VarNameStoreUnregister(lua->flowvar[i], VAR_TYPE_FLOW_VAR); } + for (uint16_t i = 0; i < lua->bytevars; i++) { + SCFree(lua->bytevar[i].name); + } DetectUnregisterThreadCtxFuncs(de_ctx, lua, "lua"); diff --git a/src/detect-lua.h b/src/detect-lua.h index a0cb265145..c67ed7e1b4 100644 --- a/src/detect-lua.h +++ b/src/detect-lua.h @@ -36,6 +36,11 @@ typedef struct DetectLuaThreadData { #define DETECT_LUA_MAX_FLOWINTS 15 #define DETECT_LUA_MAX_BYTEVARS 15 +typedef struct DetectLuaDataBytevarEntry_ { + char *name; + uint32_t id; +} DetectLuaDataBytevarEntry; + typedef struct DetectLuaData { int thread_ctx_id; int negated; @@ -47,7 +52,7 @@ typedef struct DetectLuaData { uint16_t flowvars; uint32_t flowvar[DETECT_LUA_MAX_FLOWVARS]; uint16_t bytevars; - uint32_t bytevar[DETECT_LUA_MAX_BYTEVARS]; + DetectLuaDataBytevarEntry bytevar[DETECT_LUA_MAX_BYTEVARS]; uint64_t alloc_limit; uint64_t instruction_limit; int allow_restricted_functions; diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index f0898c13b5..6682917204 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -18,6 +18,7 @@ #include "suricata-common.h" #include "util-lua-builtins.h" #include "util-lua-base64lib.h" +#include "util-lua-bytevarlib.h" #include "util-lua-config.h" #include "util-lua-dataset.h" #include "util-lua-dnp3.h" @@ -40,6 +41,7 @@ static const luaL_Reg builtins[] = { { "suricata.base64", SCLuaLoadBase64Lib }, + { "suricata.bytevar", LuaLoadBytevarLib }, { "suricata.config", SCLuaLoadConfigLib }, { "suricata.dataset", LuaLoadDatasetLib }, { "suricata.dnp3", SCLuaLoadDnp3Lib }, diff --git a/src/util-lua-bytevarlib.c b/src/util-lua-bytevarlib.c new file mode 100644 index 0000000000..e310f43f6d --- /dev/null +++ b/src/util-lua-bytevarlib.c @@ -0,0 +1,127 @@ +/* Copyright (C) 2025 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "suricata-common.h" +#include "detect-byte.h" +#include "util-lua-common.h" +#include "util-lua-bytevarlib.h" +#include "util-lua.h" +#include "detect-lua.h" +#include "detect-lua-extensions.h" + +#include "lauxlib.h" + +static const char suricata_bytevar_mt[] = "suricata:bytevar:mt"; + +static DetectLuaData *GetLuaData(lua_State *luastate) +{ + DetectLuaData *ld; + lua_pushlightuserdata(luastate, (void *)&luaext_key_ld); + lua_gettable(luastate, LUA_REGISTRYINDEX); + ld = lua_touserdata(luastate, -1); + return ld; +} + +static int LuaBytevarMap(lua_State *L) +{ + const Signature *s = lua_touserdata(L, -2); + const char *name = luaL_checkstring(L, -1); + DetectLuaData *ld = GetLuaData(L); + + /* Is this name already mapped? */ + for (uint16_t i = 0; i < ld->bytevars; i++) { + if (strcmp(ld->bytevar[i].name, name) == 0) { + lua_pushinteger(L, ld->bytevar[i].id); + return 1; + } + } + + if (ld->bytevars == DETECT_LUA_MAX_BYTEVARS) { + luaL_error(L, "too many bytevars mapped"); + } + + DetectByteIndexType idx; + if (!DetectByteRetrieveSMVar(name, s, &idx)) { + luaL_error(L, "unknown byte_extract or byte_math variable: %s", name); + } + + ld->bytevar[ld->bytevars].name = SCStrdup(name); + if (ld->bytevar[ld->bytevars].name == NULL) { + luaL_error(L, "failed to allocate memory for bytevar name: %s", name); + } + ld->bytevar[ld->bytevars++].id = idx; + + return 1; +} + +static int LuaBytevarGet(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + DetectLuaData *ld = GetLuaData(L); + if (ld == NULL) { + return luaL_error(L, "internal error: no lua data"); + } + + for (uint16_t i = 0; i < ld->bytevars; i++) { + if (strcmp(ld->bytevar[i].name, name) == 0) { + uint32_t *bytevar_id = lua_newuserdata(L, sizeof(*bytevar_id)); + *bytevar_id = ld->bytevar[i].id; + luaL_getmetatable(L, suricata_bytevar_mt); + lua_setmetatable(L, -2); + return 1; + } + } + + return luaL_error(L, "unknown bytevar: %s", name); +} + +static int LuaBytevarValue(lua_State *L) +{ + uint32_t *bytevar_id = luaL_checkudata(L, 1, suricata_bytevar_mt); + DetectEngineThreadCtx *det_ctx = LuaStateGetDetCtx(L); + if (det_ctx == NULL) { + return LuaCallbackError(L, "internal error: no det_ctx"); + } + lua_pushinteger(L, det_ctx->byte_values[*bytevar_id]); + return 1; +} + +static const luaL_Reg bytevarlib[] = { + // clang-format off + { "map", LuaBytevarMap, }, + { "get", LuaBytevarGet, }, + { NULL, NULL, }, + // clang-format on +}; + +static const luaL_Reg bytevarmt[] = { + // clang-format off + { "value", LuaBytevarValue, }, + { NULL, NULL, }, + // clang-format on +}; + +int LuaLoadBytevarLib(lua_State *L) +{ + luaL_newmetatable(L, suricata_bytevar_mt); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, bytevarmt, 0); + + luaL_newlib(L, bytevarlib); + return 1; +} diff --git a/src/util-lua-bytevarlib.h b/src/util-lua-bytevarlib.h new file mode 100644 index 0000000000..4806e80825 --- /dev/null +++ b/src/util-lua-bytevarlib.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2025 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef SURICATA_UTIL_LUA_BYTEVARLIB_H +#define SURICATA_UTIL_LUA_BYTEVARLIB_H + +#include "lua.h" + +int LuaLoadBytevarLib(lua_State *L); + +#endif /* SURICATA_UTIL_LUA_BYTEVARLIB_H */