]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua/bytevar: convert SCByteVar to Lua lib
authorJason Ish <jason.ish@oisf.net>
Mon, 2 Jun 2025 20:37:57 +0000 (14:37 -0600)
committerVictor Julien <victor@inliniac.net>
Wed, 4 Jun 2025 07:39:51 +0000 (09:39 +0200)
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 [new file with mode: 0644]
doc/userguide/lua/libs/index.rst
doc/userguide/lua/lua-functions.rst
src/Makefile.am
src/detect-lua-extensions.c
src/detect-lua.c
src/detect-lua.h
src/util-lua-builtins.c
src/util-lua-bytevarlib.c [new file with mode: 0644]
src/util-lua-bytevarlib.h [new file with mode: 0644]

diff --git a/doc/userguide/lua/libs/bytevar.rst b/doc/userguide/lua/libs/bytevar.rst
new file mode 100644 (file)
index 0000000..0b9221a
--- /dev/null
@@ -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
index cc63fb99c8fc18551bc05932cf675efbc8752b79..188ca1784f330e39973c1c6596a7a35a57af069f 100644 (file)
@@ -9,6 +9,7 @@ environment without access to additional modules.
 .. toctree::
 
    base64
+   bytevar
    config
    dns
    file
index 8a23957e4171d1e4e4f3fa29c3b377233a3221e7..9f87197670960ed4e9adf4ff58266fc6a4eba1a8 100644 (file)
@@ -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)
index 44ba243ff924058432634db72700923dc0ee083b..9dde1185b69fd796a4969782300d1449a864b5ae 100755 (executable)
@@ -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 \
index ea415404d891da89da279f1731a52b755b05ff28..e8c810d0b6e4ccab1beb0dddf739a6aee62a2d7c 100644 (file)
 
 #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;
 }
index ac2b6dc3f3265b15fbff65086732d8b370de05f2..92e1e80eecb4f62840db76ab2d4e92cecfb164cb 100644 (file)
@@ -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");
 
index a0cb26514518c4b99bab582e98426db88e76d5bf..c67ed7e1b4e5ccb4c59da6870a5ed6454fc96e8b 100644 (file)
@@ -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;
index f0898c13b55320d868c719120b4f2c4728ac5dc3..6682917204acb267e671bc16b6a4634488b7b796 100644 (file)
@@ -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 (file)
index 0000000..e310f43
--- /dev/null
@@ -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 (file)
index 0000000..4806e80
--- /dev/null
@@ -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 */