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