]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua: use a function allow list instead of a deny list
authorJason Ish <jason.ish@oisf.net>
Thu, 23 May 2024 17:55:28 +0000 (11:55 -0600)
committerJason Ish <jason.ish@oisf.net>
Mon, 27 May 2024 22:44:54 +0000 (16:44 -0600)
The Lua library surface area is small enough to manage an allow list,
which is generally better than a deny list, as we'll explicitly need
to opt-in to new functions provided by the Lua runtime.

src/detect-lua.c
src/util-lua-sandbox.c
src/util-lua-sandbox.h

index 07ebe57e5311c22a7357ebc75e6ebfea02ce6aa8..865df5d5ce3bd60eb822f92440a9a16c35fb950f 100644 (file)
@@ -469,7 +469,7 @@ static void *DetectLuaThreadInit(void *data)
     if (lua->allow_restricted_functions) {
         luaL_openlibs(t->luastate);
     } else {
-        SCLuaSbLoadRestricted(t->luastate);
+        SCLuaSbLoadLibs(t->luastate);
     }
 
     LuaRegisterExtensions(t->luastate);
@@ -572,7 +572,7 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld, const
     if (ld->allow_restricted_functions) {
         luaL_openlibs(luastate);
     } else {
-        SCLuaSbLoadRestricted(luastate);
+        SCLuaSbLoadLibs(luastate);
     }
 
     /* hackish, needed to allow unittests to pass buffers as scripts instead of files */
index 49e2b7969e8ec71f4c22c6f472f729162786d368..690fef8881a86b433af8ed792e15f045e3402ef9 100644 (file)
@@ -26,6 +26,7 @@
 #include "lua.h"
 #include "lauxlib.h"
 #include "lualib.h"
+#include "util-debug.h"
 
 #include "util-debug.h"
 #include "util-validate.h"
 
 #define SANDBOX_CTX "SANDBOX_CTX"
 
-typedef struct BlockedFunction {
-    const char *module;
-    const char *name;
-} BlockedFunction;
-
 static void HookFunc(lua_State *L, lua_Debug *ar);
 static int OpenSandbox(lua_State *L);
 
@@ -79,6 +75,160 @@ static void *LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
     }
 }
 
+/**
+ * Function put in place of Lua functions that are blocked.
+ *
+ * TODO: Might want to create a version of this for each library that
+ * has blocked functions, so it can display the name of the
+ * library. As it doesn't appear that can be retrieved.
+ */
+static int LuaBlockedFunction(lua_State *L)
+{
+    lua_Debug ar;
+    lua_getstack(L, 0, &ar);
+    lua_getinfo(L, "n", &ar);
+    if (ar.name) {
+        luaL_error(L, "Blocked Lua function called: %s", ar.name);
+    } else {
+        luaL_error(L, "Blocked Lua function: name not available");
+    }
+    return -1;
+}
+
+/**
+ * Check if a Lua function in a specific module is allowed.
+ *
+ * This is essentially an allow list for Lua functions.
+ */
+static bool IsAllowed(const char *module, const char *fname)
+{
+    static const char *base_allowed[] = {
+        "assert",
+        "ipairs",
+        "next",
+        "pairs",
+        "print",
+        "rawequal",
+        "rawlen",
+        "select",
+        "tonumber",
+        "tostring",
+        "type",
+        "warn",
+        "rawget",
+        "rawset",
+        "error",
+        NULL,
+    };
+
+    /* Allow all. */
+    static const char *table_allowed[] = {
+        "concat",
+        "insert",
+        "move",
+        "pack",
+        "remove",
+        "sort",
+        "unpack",
+        NULL,
+    };
+
+    /* Allow all. */
+    static const char *string_allowed[] = {
+        "byte",
+        "char",
+        "dump",
+        "find",
+        "format",
+        "gmatch",
+        "gsub",
+        "len",
+        "lower",
+        "match",
+        "pack",
+        "packsize",
+        "rep",
+        "reverse",
+        "sub",
+        "unpack",
+        "upper",
+        NULL,
+    };
+
+    /* Allow all. */
+    static const char *math_allowed[] = {
+        "abs",
+        "acos",
+        "asin",
+        "atan",
+        "atan2",
+        "ceil",
+        "cos",
+        "cosh",
+        "deg",
+        "exp",
+        "floor",
+        "fmod",
+        "frexp",
+        "ldexp",
+        "log",
+        "log10",
+        "max",
+        "min",
+        "modf",
+        "pow",
+        "rad",
+        "random",
+        "randomseed",
+        "sin",
+        "sinh",
+        "sqrt",
+        "tan",
+        "tanh",
+        "tointeger",
+        "type",
+        "ult",
+        NULL,
+    };
+
+    /* Allow all. */
+    static const char *utf8_allowed[] = {
+        "offset",
+        "len",
+        "codes",
+        "char",
+        "codepoint",
+        NULL,
+    };
+
+    const char **allowed = NULL;
+
+    if (strcmp(module, LUA_GNAME) == 0) {
+        allowed = base_allowed;
+    } else if (strcmp(module, LUA_TABLIBNAME) == 0) {
+        allowed = table_allowed;
+    } else if (strcmp(module, LUA_STRLIBNAME) == 0) {
+        allowed = string_allowed;
+    } else if (strcmp(module, LUA_MATHLIBNAME) == 0) {
+        allowed = math_allowed;
+    } else if (strcmp(module, LUA_UTF8LIBNAME) == 0) {
+        allowed = utf8_allowed;
+    } else {
+        /* This is a programming error. */
+        FatalError("Unknown Lua module %s", module);
+    }
+
+    if (allowed) {
+        for (int i = 0; allowed[i] != NULL; i++) {
+            if (strcmp(allowed[i], fname) == 0) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 /**
  * Set of libs that are allowed and loaded into the Lua state.
  */
@@ -89,83 +239,56 @@ static const luaL_Reg AllowedLibs[] = {
     { LUA_STRLIBNAME, luaopen_string },
     { LUA_MATHLIBNAME, luaopen_math },
     { LUA_UTF8LIBNAME, luaopen_utf8 },
-
-    /* TODO: Review these libs... */
-#if 0
-    {LUA_LOADLIBNAME, luaopen_package},
-    {LUA_COLIBNAME, luaopen_coroutine},
-    {LUA_IOLIBNAME, luaopen_io},
-    {LUA_OSLIBNAME, luaopen_os},
-#endif
-
-    /* What is this for? */
-    { LUA_DBLIBNAME, OpenSandbox }, // TODO: remove this from restricted
-
     { NULL, NULL }
     // clang-format on
 };
 
-// TODO: should we block raw* functions?
-// TODO: Will we ever need to block a subset of functions more than one level deep?
-static const BlockedFunction BlockedFunctions[] = {
-    // clang-format off
-    { LUA_GNAME, "collectgarbage" },
-    { LUA_GNAME, "dofile" },
-    { LUA_GNAME, "getmetatable" },
-    { LUA_GNAME, "loadfile" },
-    { LUA_GNAME, "load" }, 
-    { LUA_GNAME, "pcall" },
-    { LUA_GNAME, "setmetatable" },
-    { LUA_GNAME, "xpcall" },
-
-    /* TODO:  probably don't need to block this for normal restricted
-     * since we have memory limit */
-    { LUA_STRLIBNAME, "rep" }, 
-    { NULL, NULL }
-    // clang-format on
-};
-
-static void LoadAllowedLibs(lua_State *L, const luaL_Reg *libs)
+/**
+ * Load allowed Lua libraries into the state.
+ *
+ * Functions from each library that are not in the allowed list are
+ * replaced with LuaBlockedFunction.
+ */
+void SCLuaSbLoadLibs(lua_State *L)
 {
     const luaL_Reg *lib;
-    /* "require" functions from 'loadedlibs' and set results to global table */
-    for (lib = libs; lib->func; lib++) {
+
+    for (lib = AllowedLibs; lib->func; lib++) {
         luaL_requiref(L, lib->name, lib->func, 1);
-        lua_pop(L, 1); /* remove lib */
+        lua_pop(L, 1);
+        /* Iterate over all the functions in the just loaded table and
+         * replace functions now on the allow list with our blocked
+         * function placeholder. */
+        lua_getglobal(L, lib->name);
+        lua_pushnil(L);
+        while (lua_next(L, -2)) {
+            if (lua_type(L, -1) == LUA_TFUNCTION) {
+                const char *name = lua_tostring(L, -2);
+                if (!IsAllowed(lib->name, name)) {
+                    SCLogDebug("Blocking Lua function %s.%s", lib->name, name);
+                    lua_pushstring(L, name);
+                    lua_pushcfunction(L, LuaBlockedFunction);
+                    lua_settable(L, -5);
+                } else {
+                    SCLogDebug("Allowing Lua function %s.%s", lib->name, name);
+                }
+            }
+            lua_pop(L, 1);
+        }
+        lua_pop(L, 1);
     }
 }
 
 /**
- * Apply function blocking by replacing blocked functions with a nil.
+ * \brief Allocate a new Lua sandbox.
+ *
+ * \returns An allocated sandbox state or NULL if memory allocation
+ *     fails.
  */
-static void ApplyBlockedFunctions(lua_State *L, const BlockedFunction *funcs)
-{
-    const BlockedFunction *func;
-
-    // set target functions to nil
-    lua_pushglobaltable(L);
-    for (func = funcs; func->module; func++) {
-        lua_pushstring(L, func->module);
-        lua_gettable(L, -2); // load module to stack
-        lua_pushstring(L, func->name);
-        lua_pushnil(L);
-        lua_settable(L, -3);
-        lua_pop(L, 1); // remove module from the stack
-    }
-    lua_pop(L, 1); // remove global table
-}
-
-void SCLuaSbLoadRestricted(lua_State *L)
-{
-    LoadAllowedLibs(L, AllowedLibs);
-    ApplyBlockedFunctions(L, BlockedFunctions);
-}
-
 lua_State *SCLuaSbStateNew(uint64_t alloclimit, uint64_t instructionlimit)
 {
     SCLuaSbState *sb = SCCalloc(1, sizeof(SCLuaSbState));
     if (sb == NULL) {
-        // Out of memory.  Error code?
         return NULL;
     }
 
index 2d285507994d402ed5367713b5d67f8c2706000e..94344f1970d71f291885658107361d252f952e47 100644 (file)
@@ -74,6 +74,6 @@ void SCLuaSbResetInstructionCounter(lua_State *sb);
  *  Replaces luaL_openlibs.  Only opens allowed packages for the sandbox and
  *  masks out dangerous functions from the base.
  */
-void SCLuaSbLoadRestricted(lua_State *L);
+void SCLuaSbLoadLibs(lua_State *L);
 
 #endif /*  SURICATA_UTIL_LUA_SANDBOX_H */