]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua: convert flowvar functions to lib 13097/head
authorJason Ish <jason.ish@oisf.net>
Thu, 24 Apr 2025 18:11:28 +0000 (12:11 -0600)
committerJason Ish <jason.ish@oisf.net>
Mon, 28 Apr 2025 00:58:47 +0000 (18:58 -0600)
New Lua lib, "suricata.flowvar" for working with flowvars from Lua.

Replaces functions:
- SCFlowvarGet (and ScFlowvarGet)
- SCFlowvarSet (and SCFlowvarSet)

Of note, the DetectLuaData has been made available to the init and
thread_init methods, instead of just the match. This is due to an
issue that if a flow variable is not registered in init, it will not
be logged, registering in thread_init is too late.

Ticket: #7486

doc/userguide/lua/libs/flowvar.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-extensions.h
src/detect-lua.c
src/util-lua-builtins.c
src/util-lua-flowvarlib.c [new file with mode: 0644]
src/util-lua-flowvarlib.h [new file with mode: 0644]

diff --git a/doc/userguide/lua/libs/flowvar.rst b/doc/userguide/lua/libs/flowvar.rst
new file mode 100644 (file)
index 0000000..0822bc3
--- /dev/null
@@ -0,0 +1,79 @@
+Flowvar
+#######
+
+The ``suricata.flowvar`` library exposes flow variables to Lua
+scripts.
+
+Initialization
+--------------
+
+First, the ``flowvar`` lib module must be loaded::
+
+  local flowvarlib = require("suricata.flow")
+
+Then in the ``init`` method, any flow variables used in the script
+should be registered. This is optional and could be skipped if you
+know for sure the flow variable will be registered by some other
+means.
+
+Example::
+
+  local flowvarlib = require("suricata.flow")
+
+  function init ()
+      flowvarlib.register("count")
+      return {}
+  end
+
+Finally, in the ``thread_init`` function a handle is acquired for the
+flow variables and stored as a global::
+
+  function thread_init ()
+      count_flow_var = flowvarlib.get("count")
+  end
+
+Flow Variable Methods
+---------------------
+
+``value()``
+^^^^^^^^^^^
+
+Get the current value of the flow variable as a string. Note that
+``nil`` may be returned if the flow variable does not have a value.
+
+``set(value, len)``
+^^^^^^^^^^^^^^^^^^^
+
+Set the value of the flow variable to the value provided. The length
+of the value must also be provided.
+
+Example
+-------
+
+::
+
+  local flowvarlib = require("suricata.flowvar")
+
+  function init ()
+      flowvarlib.register("count")
+      return {}
+  end
+
+  function thread_init ()
+      count_var = flowvarlib.get("count")
+  end
+
+  function match ()
+      local value = count_var:value()
+      if value == nil then
+          -- Initialize value to 1.
+          value = tostring(1)
+          count_var:set(value, #value)
+      else
+          value = tostring(tonumber(value) + 1)
+          count_var:set(value, #value)
+      fi
+
+      -- Return 1 or 0 based on your own logic.
+      return 1
+  end
index c043da27ad441ab70f810da711884070f5f2bede..2279b74175517735b13a3aac646df8fd1a390973 100644 (file)
@@ -11,6 +11,7 @@ environment without access to additional modules.
    base64
    dns
    flowlib
+   flowvar
    hashlib
    http
    packetlib
index 9f02125af848eda46c5856a86d643be4a651f2ef..120b2ad01e4511ccb70e1346fa3ba2c4c8b0a3e7 100644 (file)
@@ -675,38 +675,6 @@ SCFlowintDecr
 
 Decrement Flowint at index given by the first parameter.
 
-SCFlowvarGet
-~~~~~~~~~~~~
-
-Get the Flowvar at index given by the parameter.
-
-SCFlowvarSet
-~~~~~~~~~~~~
-
-Set a Flowvar. First parameter is the index, second is the data
-and third is the length of data.
-
-You can use it to set string 
-
-::
-
- function init (args)
-     local needs = {}
-     needs["http.request_headers"] = tostring(true)
-     needs["flowvar"] = {"cnt"}
-     return needs
- end
- function match(args)
-     a = SCFlowvarGet(0);
-     if a then
-         a = tostring(tonumber(a)+1)
-         SCFlowvarSet(0, a, #a)
-     else
-         a = tostring(1)
-         SCFlowvarSet(0, a, #a)
-     end 
-
 Misc
 ----
 
index 19f8557b39f00a4e2bd8910f42cce16d515b6375..1d02693a0c54f90b1e1b78f7571ed5c2aa8967fe 100755 (executable)
@@ -526,6 +526,7 @@ noinst_HEADERS = \
        util-lua-dnp3-objects.h \
        util-lua-dns.h \
        util-lua-flowlib.h \
+       util-lua-flowvarlib.h \
        util-lua.h \
        util-lua-hashlib.h \
        util-lua-hassh.h \
@@ -1094,6 +1095,7 @@ libsuricata_c_a_SOURCES = \
        util-lua-dnp3-objects.c \
        util-lua-dns.c \
        util-lua-flowlib.c \
+       util-lua-flowvarlib.c \
        util-lua-hashlib.c \
        util-lua-hassh.c \
        util-lua-http.c \
index ba51db8c7e2c8dd1cbe77cc5412d945eda7c7ca0..dd6a9736b3c07eebf2bed6c6a9245528ab0594b3 100644 (file)
@@ -47,7 +47,8 @@
 #include "util-lua-dnp3.h"
 #include "detect-lua-extensions.h"
 
-static const char luaext_key_ld[] = "suricata:luadata";
+/* Lua registry key for DetectLuaData. */
+const char luaext_key_ld[] = "suricata:luadata";
 
 static int GetLuaData(lua_State *luastate, DetectLuaData **ret_ld)
 {
@@ -74,67 +75,6 @@ static int GetFlow(lua_State *luastate, Flow **ret_f)
     return 0;
 }
 
-static int GetFlowVarById(lua_State *luastate, Flow *f,
-        FlowVar **ret_fv, bool fv_may_be_null, uint32_t *ret_idx)
-{
-    DetectLuaData *ld = NULL;
-    if (ret_idx)
-        *ret_idx = 0;
-    *ret_fv = NULL;
-
-    /* need lua data for id -> idx conversion */
-    int ret = GetLuaData(luastate, &ld);
-    if (ret != 0)
-        return ret;
-
-    if (!lua_isnumber(luastate, 1)) {
-        LUA_ERROR("flowvar id not a number");
-    }
-    int id = lua_tonumber(luastate, 1);
-    if (id < 0 || id >= DETECT_LUA_MAX_FLOWVARS) {
-        LUA_ERROR("flowvar id out of range");
-    }
-    uint32_t idx = ld->flowvar[id];
-    if (idx == 0) {
-        LUA_ERROR("flowvar id uninitialized");
-    }
-    FlowVar *fv = FlowVarGet(f, idx);
-    if (!fv_may_be_null && fv == NULL) {
-        LUA_ERROR("no flow var");
-    }
-    *ret_fv = fv;
-    if (ret_idx)
-        *ret_idx = idx;
-    return 0;
-}
-
-static int GetFlowVarByKey(lua_State *luastate, Flow *f, FlowVar **ret_fv)
-{
-    *ret_fv = NULL;
-
-    if (!lua_isstring(luastate, 1)) {
-        LUA_ERROR("flowvar key not a string");
-    }
-    const char *keystr = lua_tostring(luastate, 1);
-    if (keystr == NULL) {
-        LUA_ERROR("key is null");
-    }
-    if (!lua_isnumber(luastate, 2)) {
-        LUA_ERROR("key length not specified");
-    }
-    int keylen = lua_tonumber(luastate, 2);
-    if (keylen < 0 || keylen > 0xff) {
-        LUA_ERROR("key len out of range: max 256");
-    }
-
-    FlowVar *fv = FlowVarGetByKey(f, (const uint8_t *)keystr, (uint8_t)keylen);
-    if (fv == NULL) {
-        LUA_ERROR("no flow var");
-    }
-    *ret_fv = fv;
-    return 0;
-}
-
 static int GetFlowIntById(lua_State *luastate, Flow *f,
         FlowVar **ret_fv, bool fv_may_be_null, uint32_t *ret_idx)
 {
@@ -169,151 +109,6 @@ static int GetFlowIntById(lua_State *luastate, Flow *f,
     return 0;
 }
 
-static int LuaGetFlowvar(lua_State *luastate)
-{
-    Flow *f;
-    FlowVar *fv;
-    int ret;
-
-    /* need flow */
-    ret = GetFlow(luastate, &f);
-    if (ret != 0)
-        return ret;
-
-    if (lua_isnumber(luastate, 1)) {
-        ret = GetFlowVarById(luastate, f, &fv, false, NULL);
-        if (ret != 0 || fv == NULL)
-            return ret;
-    } else if (lua_isstring(luastate, 1)) {
-        ret = GetFlowVarByKey(luastate, f, &fv);
-        if (ret != 0 || fv == NULL)
-            return ret;
-    } else {
-        LUA_ERROR("invalid data type as first argument");
-    }
-
-    LuaPushStringBuffer(luastate,
-            (const uint8_t *)fv->data.fv_str.value,
-            (size_t)fv->data.fv_str.value_len);
-    return 1;
-}
-
-static int LuaSetFlowvarById(lua_State *luastate)
-{
-    uint32_t idx = 0;
-    Flow *f;
-    const char *str;
-    int len;
-    uint8_t *buffer;
-    FlowVar *fv = NULL;
-
-    /* need flow */
-    int ret = GetFlow(luastate, &f);
-    if (ret != 0)
-        return ret;
-
-    ret = GetFlowVarById(luastate, f, &fv, true, &idx);
-    if (ret != 0)
-        return ret;
-
-    if (!lua_isstring(luastate, 2)) {
-        LUA_ERROR("buffer not a string");
-    }
-    str = lua_tostring(luastate, 2);
-    if (str == NULL) {
-        LUA_ERROR("buffer is null");
-    }
-
-    if (!lua_isnumber(luastate, 3)) {
-        LUA_ERROR("buffer length not specified");
-    }
-    len = lua_tonumber(luastate, 3);
-    if (len < 0 || len > 0xffff) {
-        LUA_ERROR("len out of range: max 64k");
-    }
-
-    buffer = SCMalloc(len+1);
-    if (unlikely(buffer == NULL)) {
-        LUA_ERROR("out of memory");
-    }
-    memcpy(buffer, str, len);
-    buffer[len] = '\0';
-
-    FlowVarAddIdValue(f, idx, buffer, (uint16_t)len);
-    return 0;
-}
-
-static int LuaSetFlowvarByKey(lua_State *luastate)
-{
-    Flow *f;
-    const char *str;
-    int len;
-    uint8_t *buffer;
-
-    /* need flow */
-    int ret = GetFlow(luastate, &f);
-    if (ret != 0)
-        return ret;
-
-    const char *keystr = NULL;
-    int keylen = 0;
-
-    keystr = lua_tostring(luastate, 1);
-    if (keystr == NULL) {
-        LUA_ERROR("key is null");
-    }
-    if (!lua_isnumber(luastate, 2)) {
-        LUA_ERROR("key length not specified");
-    }
-    keylen = lua_tonumber(luastate, 2);
-    if (keylen < 0 || keylen > 0xff) {
-        LUA_ERROR("key len out of range: max 255");
-    }
-
-    if (!lua_isstring(luastate, 3)) {
-        LUA_ERROR("buffer not a string");
-    }
-    str = lua_tostring(luastate, 3);
-    if (str == NULL) {
-        LUA_ERROR("buffer is null");
-    }
-
-    if (!lua_isnumber(luastate, 4)) {
-        LUA_ERROR("buffer length not specified");
-    }
-    len = lua_tonumber(luastate, 4);
-    if (len < 0 || len > 0xffff) {
-        LUA_ERROR("len out of range: max 64k");
-    }
-
-    buffer = SCMalloc(len+1);
-    if (unlikely(buffer == NULL)) {
-        LUA_ERROR("out of memory");
-    }
-    memcpy(buffer, str, len);
-    buffer[len] = '\0';
-
-    uint8_t *keybuf = SCMalloc(keylen+1);
-    if (unlikely(keybuf == NULL)) {
-        SCFree(buffer);
-        LUA_ERROR("out of memory");
-    }
-    memcpy(keybuf, keystr, keylen);
-    keybuf[keylen] = '\0';
-    FlowVarAddKeyValue(f, keybuf, (uint8_t)keylen, buffer, (uint16_t)len);
-
-    return 0;
-}
-
-static int LuaSetFlowvar(lua_State *luastate)
-{
-    if (lua_isnumber(luastate, 1)) {
-        return LuaSetFlowvarById(luastate);
-    } else {
-        return LuaSetFlowvarByKey(luastate);
-    }
-}
-
 static int LuaGetFlowint(lua_State *luastate)
 {
     Flow *f;
@@ -478,13 +273,7 @@ void LuaExtensionsMatchSetup(lua_State *lua_state, DetectLuaData *ld,
 {
     SCLogDebug("det_ctx %p, f %p", det_ctx, f);
 
-    /* lua keyword data */
-    lua_pushlightuserdata(lua_state, (void *)&luaext_key_ld);
-    lua_pushlightuserdata(lua_state, (void *)ld);
-    lua_settable(lua_state, LUA_REGISTRYINDEX);
-
     LuaStateSetSignature(lua_state, s);
-
     LuaStateSetFlow(lua_state, f);
     LuaStateSetDetCtx(lua_state, det_ctx);
 
@@ -508,18 +297,6 @@ void LuaExtensionsMatchSetup(lua_State *lua_state, DetectLuaData *ld,
  */
 int LuaRegisterExtensions(lua_State *lua_state)
 {
-    lua_pushcfunction(lua_state, LuaGetFlowvar);
-    lua_setglobal(lua_state, "ScFlowvarGet");
-
-    lua_pushcfunction(lua_state, LuaGetFlowvar);
-    lua_setglobal(lua_state, "SCFlowvarGet");
-
-    lua_pushcfunction(lua_state, LuaSetFlowvar);
-    lua_setglobal(lua_state, "ScFlowvarSet");
-
-    lua_pushcfunction(lua_state, LuaSetFlowvar);
-    lua_setglobal(lua_state, "SCFlowvarSet");
-
     lua_pushcfunction(lua_state, LuaGetFlowint);
     lua_setglobal(lua_state, "ScFlowintGet");
 
index 7a631a15a3b9bd98f4f5ee24558224c5ef50fae9..3620fd084993b4fd9fd2c9bcc92448a15b35242b 100644 (file)
@@ -24,6 +24,8 @@
 #ifndef SURICATA_DETECT_LUA_EXT_H
 #define SURICATA_DETECT_LUA_EXT_H
 
+extern const char luaext_key_ld[];
+
 int LuaRegisterExtensions(lua_State *);
 
 void LuaExtensionsMatchSetup(lua_State *lua_state, DetectLuaData *, DetectEngineThreadCtx *det_ctx,
index 1ba922788ba05b10868a8f889ff7b965f42a7d31..9137afeba1ba2946c14b7810ff5c260266d36a12 100644 (file)
@@ -146,6 +146,13 @@ void LuaDumpStack(lua_State *state, const char *prefix)
     }
 }
 
+static void LuaStateSetDetectLuaData(lua_State *state, DetectLuaData *data)
+{
+    lua_pushlightuserdata(state, (void *)&luaext_key_ld);
+    lua_pushlightuserdata(state, (void *)data);
+    lua_settable(state, LUA_REGISTRYINDEX);
+}
+
 /**
  * \brief Common function to run the Lua match function and process
  *     the return value.
@@ -406,6 +413,7 @@ static void *DetectLuaThreadInit(void *data)
     }
 
     LuaRegisterExtensions(t->luastate);
+    LuaStateSetDetectLuaData(t->luastate, lua);
 
     /* hackish, needed to allow unittests to pass buffers as scripts instead of files */
 #ifdef UNITTESTS
@@ -513,6 +521,7 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const
     } else {
         SCLuaSbLoadLibs(luastate);
     }
+    LuaStateSetDetectLuaData(luastate, ld);
 
     /* hackish, needed to allow unittests to pass buffers as scripts instead of files */
 #ifdef UNITTESTS
@@ -804,22 +813,25 @@ static int LuaMatchTest01(void)
 {
     SCConfSetFinal("security.lua.allow-rules", "true");
 
-    const char script[] = "function init (args)\n"
-                          "   local needs = {}\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
-                          "   return needs\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")\n"
+                          "   return {}\n"
+                          "end\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")\n"
                           "end\n"
                           "\n"
                           "function match(args)\n"
-                          "   a = ScFlowvarGet(0)\n"
+                          "   a = cnt:value()\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
@@ -926,22 +938,25 @@ static int LuaMatchTest01(void)
 
 static int LuaMatchTest01a(void)
 {
-    const char script[] = "function init (args)\n"
-                          "   local needs = {}\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
-                          "   return needs\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")\n"
+                          "   return {}\n"
+                          "end\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")\n"
                           "end\n"
                           "\n"
                           "function match(args)\n"
-                          "   a = SCFlowvarGet(0)\n"
+                          "   a = cnt:value(0)\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
@@ -1047,23 +1062,27 @@ static int LuaMatchTest01a(void)
 /** \test payload buffer */
 static int LuaMatchTest02(void)
 {
-    const char script[] = "function init (args)\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")\n"
                           "   local needs = {}\n"
                           "   needs[\"payload\"] = tostring(true)\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
                           "   return needs\n"
                           "end\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")\n"
+                          "end\n"
                           "\n"
                           "function match(args)\n"
-                          "   a = ScFlowvarGet(0)\n"
+                          "   a = cnt:value()\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
@@ -1154,23 +1173,27 @@ static int LuaMatchTest02(void)
 /** \test payload buffer */
 static int LuaMatchTest02a(void)
 {
-    const char script[] = "function init (args)\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")"
                           "   local needs = {}\n"
                           "   needs[\"payload\"] = tostring(true)\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
                           "   return needs\n"
                           "end\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")"
+                          "end\n"
                           "\n"
                           "function match(args)\n"
-                          "   a = SCFlowvarGet(0)\n"
+                          "   a = cnt:value()\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
@@ -1260,23 +1283,28 @@ static int LuaMatchTest02a(void)
 /** \test packet buffer */
 static int LuaMatchTest03(void)
 {
-    const char script[] = "function init (args)\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")\n"
                           "   local needs = {}\n"
                           "   needs[\"packet\"] = tostring(true)\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
                           "   return needs\n"
                           "end\n"
                           "\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")\n"
+                          "end\n"
+                          "\n"
                           "function match(args)\n"
-                          "   a = ScFlowvarGet(0)\n"
+                          "   a = cnt:value()\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       ScFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
@@ -1366,23 +1394,28 @@ static int LuaMatchTest03(void)
 /** \test packet buffer */
 static int LuaMatchTest03a(void)
 {
-    const char script[] = "function init (args)\n"
+    const char script[] = "local flowvarlib = require(\"suricata.flowvar\")\n"
+                          "function init (args)\n"
+                          "   flowvarlib.register(\"cnt\")\n"
                           "   local needs = {}\n"
                           "   needs[\"packet\"] = tostring(true)\n"
-                          "   needs[\"flowvar\"] = {\"cnt\"}\n"
                           "   return needs\n"
                           "end\n"
                           "\n"
+                          "function thread_init (args)\n"
+                          "   cnt = flowvarlib.get(\"cnt\")\n"
+                          "end\n"
+                          "\n"
                           "function match(args)\n"
-                          "   a = SCFlowvarGet(0)\n"
+                          "   a = cnt:value()\n"
                           "   if a then\n"
                           "       a = tostring(tonumber(a)+1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   else\n"
                           "       a = tostring(1)\n"
                           "       print (a)\n"
-                          "       SCFlowvarSet(0, a, #a)\n"
+                          "       cnt:set(a, #a)\n"
                           "   end\n"
                           "   \n"
                           "   print (\"pre check: \" .. (a))\n"
index 2bf22ba3c1a8ae8908464d18e9243fab7dfd1c3a..7a51749f4b9a3bd6d940aba510c29c4e32ad2063 100644 (file)
@@ -20,6 +20,7 @@
 #include "util-lua-base64lib.h"
 #include "util-lua-dataset.h"
 #include "util-lua-dnp3.h"
+#include "util-lua-flowvarlib.h"
 #include "util-lua-http.h"
 #include "util-lua-dns.h"
 #include "util-lua-ssh.h"
@@ -36,6 +37,7 @@ static const luaL_Reg builtins[] = {
     { "suricata.dnp3", SCLuaLoadDnp3Lib },
     { "suricata.dns", SCLuaLoadDnsLib },
     { "suricata.flow", LuaLoadFlowLib },
+    { "suricata.flowvar", LuaLoadFlowvarLib },
     { "suricata.hashlib", SCLuaLoadHashlib },
     { "suricata.http", SCLuaLoadHttpLib },
     { "suricata.packet", LuaLoadPacketLib },
diff --git a/src/util-lua-flowvarlib.c b/src/util-lua-flowvarlib.c
new file mode 100644 (file)
index 0000000..3cd6978
--- /dev/null
@@ -0,0 +1,142 @@
+/* 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 "app-layer-protos.h"
+#include "flow-var.h"
+#include "lauxlib.h"
+#include "util-debug.h"
+#include "util-lua-common.h"
+#include "util-lua-flowvarlib.h"
+#include "util-lua.h"
+#include "util-var-name.h"
+#include "detect-lua.h"
+#include "detect-lua-extensions.h"
+
+static const char suricata_flowvar_mt[] = "suricata:flowvar: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;
+}
+
+/**
+ * \brief Register a flowvar.
+ *
+ * Ensures that a flowvar exists for the provided name, will be
+ * created if needed.
+ *
+ * The flowvar ID is returned, however as this is most likely to be
+ * used in the scripts "init" method, this ID is unlikely to be used.
+ */
+static int LuaFlowvarRegister(lua_State *L)
+{
+    DetectLuaData *ld = GetLuaData(L);
+    const char *name = luaL_checkstring(L, 1);
+    uint32_t *flowvar_id = lua_newuserdata(L, sizeof(*flowvar_id));
+    *flowvar_id = VarNameStoreRegister(name, VAR_TYPE_FLOW_VAR);
+    if (*flowvar_id == 0) {
+        return luaL_error(L, "failed to register flowvar");
+    }
+    ld->flowvar[ld->flowvars++] = *flowvar_id;
+
+    luaL_getmetatable(L, suricata_flowvar_mt);
+    lua_setmetatable(L, -2);
+
+    return 1;
+}
+
+static int LuaFlowvarGet(lua_State *L)
+{
+    const char *name = luaL_checkstring(L, 1);
+    uint32_t *flowvar_id = lua_newuserdata(L, sizeof(*flowvar_id));
+    *flowvar_id = VarNameStoreLookupByName(name, VAR_TYPE_FLOW_VAR);
+    if (*flowvar_id == 0) {
+        return luaL_error(L, "flowvar does not exist");
+    }
+
+    luaL_getmetatable(L, suricata_flowvar_mt);
+    lua_setmetatable(L, -2);
+
+    return 1;
+}
+
+static int LuaFlowvarValue(lua_State *L)
+{
+    uint32_t *flowvar_id = luaL_testudata(L, 1, suricata_flowvar_mt);
+    Flow *f = LuaStateGetFlow(L);
+    if (f == NULL) {
+        return LuaCallbackError(L, "flow is NULL");
+    }
+    FlowVar *fv = FlowVarGet(f, *flowvar_id);
+    if (fv == NULL) {
+        lua_pushnil(L);
+    } else {
+        LuaPushStringBuffer(
+                L, (const uint8_t *)fv->data.fv_str.value, (size_t)fv->data.fv_str.value_len);
+    }
+    return 1;
+}
+
+static int LuaFlowvarSet(lua_State *L)
+{
+    const char *value = luaL_checkstring(L, 2);
+    const int len = luaL_checkinteger(L, 3);
+    uint32_t *flowvar_id = luaL_checkudata(L, 1, suricata_flowvar_mt);
+    Flow *f = LuaStateGetFlow(L);
+    if (f == NULL) {
+        return luaL_error(L, "no flow");
+    }
+
+    uint8_t *buf = SCMalloc(len + 1);
+    memcpy(buf, value, len);
+    buf[len] = '\0';
+    FlowVarAddIdValue(f, *flowvar_id, buf, (uint16_t)len);
+
+    return 1;
+}
+
+static const luaL_Reg flowvarlib[] = {
+    // clang-format off
+  { "register", LuaFlowvarRegister, },
+{ "get", LuaFlowvarGet },
+  { NULL, NULL, },
+    // clang-format on
+};
+
+static const luaL_Reg flowvarmt[] = {
+    // clang-format off
+  { "value", LuaFlowvarValue, },
+  { "set", LuaFlowvarSet, },
+  { NULL, NULL, },
+    // clang-format on
+};
+
+int LuaLoadFlowvarLib(lua_State *L)
+{
+    luaL_newmetatable(L, suricata_flowvar_mt);
+    lua_pushvalue(L, -1);
+    lua_setfield(L, -2, "__index");
+    luaL_setfuncs(L, flowvarmt, 0);
+
+    luaL_newlib(L, flowvarlib);
+    return 1;
+}
diff --git a/src/util-lua-flowvarlib.h b/src/util-lua-flowvarlib.h
new file mode 100644 (file)
index 0000000..51b23a1
--- /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_FLOWVARLIB_H
+#define SURICATA_UTIL_LUA_FLOWVARLIB_H
+
+#include "lua.h"
+
+int LuaLoadFlowvarLib(lua_State *L);
+
+#endif /* SURICATA_UTIL_LUA_FLOWVARLIB_H */