Version in base suite: 5.10.0+dfsg-5 Base version: luanti_5.10.0+dfsg-5 Target version: luanti_5.10.0+dfsg-5+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/l/luanti/luanti_5.10.0+dfsg-5.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/l/luanti/luanti_5.10.0+dfsg-5+deb13u1.dsc changelog | 6 patches/Lua-api-sanitisze-the-envionment-of-safe-functions.patch | 239 ++++++++++ patches/fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch | 25 + patches/series | 2 4 files changed, 272 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgjs3cd3z/luanti_5.10.0+dfsg-5.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgjs3cd3z/luanti_5.10.0+dfsg-5+deb13u1.dsc: no acceptable signature found diff -Nru luanti-5.10.0+dfsg/debian/changelog luanti-5.10.0+dfsg/debian/changelog --- luanti-5.10.0+dfsg/debian/changelog 2025-04-18 14:01:31.000000000 +0000 +++ luanti-5.10.0+dfsg/debian/changelog 2026-04-15 17:50:18.000000000 +0000 @@ -1,3 +1,9 @@ +luanti (5.10.0+dfsg-5+deb13u1) trixie-security; urgency=medium + + * GHSA-22c4-238c-m5j4 / GHSA-g596-mf82-w8c3 (no CVE yet) + + -- Moritz Mühlenhoff Wed, 15 Apr 2026 19:50:18 +0200 + luanti (5.10.0+dfsg-5) unstable; urgency=medium * Fix cross-build and re-enable in Salsa CI (Closes: #1102644) diff -Nru luanti-5.10.0+dfsg/debian/patches/Lua-api-sanitisze-the-envionment-of-safe-functions.patch luanti-5.10.0+dfsg/debian/patches/Lua-api-sanitisze-the-envionment-of-safe-functions.patch --- luanti-5.10.0+dfsg/debian/patches/Lua-api-sanitisze-the-envionment-of-safe-functions.patch 1970-01-01 00:00:00.000000000 +0000 +++ luanti-5.10.0+dfsg/debian/patches/Lua-api-sanitisze-the-envionment-of-safe-functions.patch 2026-04-15 17:47:29.000000000 +0000 @@ -0,0 +1,239 @@ +From 53cef183e2a85a4daff84ac1a9a7946f940da8f8 Mon Sep 17 00:00:00 2001 +From: SmallJoker +Date: Thu, 19 Mar 2026 12:38:28 +0100 +Subject: [PATCH] Lua API: Sanitize the environment of safe functions (#16985) + +--- luanti-5.10.0+dfsg.orig/src/script/cpp_api/s_security.cpp ++++ luanti-5.10.0+dfsg/src/script/cpp_api/s_security.cpp +@@ -23,29 +23,63 @@ + lua_setfield(L, -2, #name); + + +-static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1) ++// Do not use directly. This is a helper function for `copy_safe`! ++// Note: Circular references are not handled. `t_global` must be absolute. ++static void recursive_copy(lua_State *L, int idx, int t_global) + { +- if (from < 0) from = lua_gettop(L) + from + 1; +- if (to < 0) to = lua_gettop(L) + to + 1; +- for (unsigned i = 0; i < (len / sizeof(list[0])); i++) { +- lua_getfield(L, from, list[i]); +- lua_setfield(L, to, list[i]); ++ if (idx < 0) idx = lua_gettop(L) + idx + 1; ++ ++ switch (lua_type(L, idx)) { ++ case LUA_TTABLE: ++ { ++ lua_newtable(L); ++ const int to = lua_gettop(L); ++ for (lua_pushnil(L); lua_next(L, idx); lua_pop(L, 1)) { ++ // Ensure the key can stay unmodified ++ switch (lua_type(L, -2)) { ++ case LUA_TNUMBER: ++ case LUA_TSTRING: ++ // accepted ++ break; ++ default: ++ luaL_error(L, "unhandled type in recursive_copy"); ++ break; ++ } ++ ++ recursive_copy(L, -1, t_global); // value ++ ++ lua_pushvalue(L, -2); ++ lua_pushvalue(L, -2); ++ lua_rawset(L, to); ++ } ++ lua_replace(L, idx); ++ } ++ break; ++ case LUA_TFUNCTION: ++ lua_pushvalue(L, t_global); ++ lua_setfenv(L, idx); ++ break; ++ default: ++ // keep value as-is ++ break; + } + } + +-static void shallow_copy_table(lua_State *L, int from=-2, int to=-1) ++static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1) + { + if (from < 0) from = lua_gettop(L) + from + 1; + if (to < 0) to = lua_gettop(L) + to + 1; +- lua_pushnil(L); +- while (lua_next(L, from) != 0) { +- assert(lua_type(L, -1) != LUA_TTABLE); +- // duplicate key and value for lua_rawset +- lua_pushvalue(L, -2); +- lua_pushvalue(L, -2); +- lua_rawset(L, to); +- lua_pop(L, 1); ++ ++ lua_pushvalue(L, LUA_GLOBALSINDEX); ++ const int t_sec = lua_gettop(L); ++ ++ for (unsigned i = 0; i < (len / sizeof(list[0])); i++) { ++ lua_getfield(L, from, list[i]); ++ recursive_copy(L, -1, t_sec); ++ lua_setfield(L, to, list[i]); + } ++ ++ lua_pop(L, 1); + } + + // Pushes the original version of a library function on the stack, from the old version +@@ -58,6 +92,11 @@ static inline void push_original(lua_Sta + lua_remove(L, -2); // Remove lib + } + ++static int l_nop(lua_State *L) ++{ ++ return 0; ++} ++ + + void ScriptApiSecurity::initializeSecurity() + { +@@ -86,8 +125,9 @@ void ScriptApiSecurity::initializeSecuri + "unpack", + "_VERSION", + "xpcall", +- }; +- static const char *whitelist_tables[] = { ++ ++ // ******** Tables whitelist ******** ++ + // These libraries are completely safe BUT we need to duplicate their table + // to ensure the sandbox can't affect the insecure env + "coroutine", +@@ -129,7 +169,6 @@ void ScriptApiSecurity::initializeSecuri + "path", + "searchpath", + }; +-#if USE_LUAJIT + static const char *jit_whitelist[] = { + "arch", + "flush", +@@ -141,23 +180,57 @@ void ScriptApiSecurity::initializeSecuri + "version", + "version_num", + }; +-#endif ++ + m_secure = true; + + lua_State *L = getStack(); ++ const int sanity_check_top = lua_gettop(L); + +- // Backup globals to the registry +- lua_getglobal(L, "_G"); +- lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); ++ /* ++ This function creates a secondary table, only accessible through ++ `core.request_insecure_environment` containing all insecure Lua functions. ++ ++ 1. Create a new table for the secure env. ++ 2. Replace the main thread env (LUA_GLOBALSINDEX) to use the secure env. ++ 3. Replace the environment of secure C functions (LUA_ENVIRONINDEX). ++ */ ++ ++ // Insecure env (stack + 1) ++ lua_pushvalue(L, LUA_GLOBALSINDEX); ++ const int old_globals = lua_gettop(L); ++ { ++ // For later use in secured functions and the insecure env ++ lua_pushvalue(L, old_globals); ++ lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); ++ } + +- // Replace the global environment with an empty one +- int thread = getThread(L); ++ // Secure env + createEmptyEnv(L); +- setLuaEnv(L, thread); ++ { ++ // Perform a full switch of the Lua state to the secure env ++ const int idx_secure = lua_gettop(L); ++ ++ int thread = getThread(L); ++ lua_pushvalue(L, idx_secure); ++ setLuaEnv(L, thread); ++ } ++ lua_pop(L, 1); + +- // Get old globals +- lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); +- int old_globals = lua_gettop(L); ++ { ++ // Security check ++ lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); ++ luaL_checktype(L, -1, LUA_TTABLE); ++ ++ lua_getglobal(L, "_G"); ++ luaL_checktype(L, -1, LUA_TTABLE); ++ FATAL_ERROR_IF(lua_rawequal(L, -1, -2), "insecure _G"); ++ ++ lua_pushcfunction(L, l_nop); ++ lua_getfenv(L, -1); ++ FATAL_ERROR_IF(lua_rawequal(L, -1, -4), "insecure env"); ++ ++ lua_pop(L, 4); ++ } + + + // Copy safe base functions +@@ -173,17 +246,6 @@ void ScriptApiSecurity::initializeSecuri + lua_pop(L, 1); + + +- // Copy safe libraries +- for (const char *libname : whitelist_tables) { +- lua_getfield(L, old_globals, libname); +- lua_newtable(L); +- shallow_copy_table(L); +- +- lua_setglobal(L, libname); +- lua_pop(L, 1); +- } +- +- + // Copy safe IO functions + lua_getfield(L, old_globals, "io"); + lua_newtable(L); +@@ -228,7 +290,7 @@ void ScriptApiSecurity::initializeSecuri + lua_setglobal(L, "package"); + lua_pop(L, 1); // Pop old package + +-#if USE_LUAJIT ++ + // Copy safe jit functions, if they exist + lua_getfield(L, -1, "jit"); + if (!lua_isnil(L, -1)) { +@@ -237,7 +299,7 @@ void ScriptApiSecurity::initializeSecuri + lua_setglobal(L, "jit"); + } + lua_pop(L, 1); // Pop old jit +-#endif ++ + + // Get rid of 'core' in the old globals, we don't want anyone thinking it's + // safe or even usable. +@@ -258,6 +320,8 @@ void ScriptApiSecurity::initializeSecuri + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + lua_pop(L, 1); // Pop empty string ++ ++ FATAL_ERROR_IF(sanity_check_top != lua_gettop(L), "unbalanced stack"); + } + + void ScriptApiSecurity::initializeSecurityClient() +@@ -268,7 +332,6 @@ void ScriptApiSecurity::initializeSecuri + "collectgarbage", + "DIR_DELIM", + "error", +- "getfenv", + "ipairs", + "next", + "pairs", diff -Nru luanti-5.10.0+dfsg/debian/patches/fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch luanti-5.10.0+dfsg/debian/patches/fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch --- luanti-5.10.0+dfsg/debian/patches/fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch 1970-01-01 00:00:00.000000000 +0000 +++ luanti-5.10.0+dfsg/debian/patches/fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch 2026-04-15 17:50:15.000000000 +0000 @@ -0,0 +1,25 @@ +From 827fd4cf7f989482b2dad381fa4afd642ea73e8c Mon Sep 17 00:00:00 2001 +From: sfan5 +Date: Sat, 21 Feb 2026 23:13:17 +0100 +Subject: [PATCH] Fix coroutine confusion with + ScriptApiSecurity::getCurrentModName + +--- luanti-5.10.0+dfsg.orig/src/script/cpp_api/s_base.cpp ++++ luanti-5.10.0+dfsg/src/script/cpp_api/s_base.cpp +@@ -240,9 +240,15 @@ std::string ScriptApiBase::getCurrentMod + // We have to make sure that this function is being called directly by + // a mod, otherwise a malicious mod could override a function and + // steal its return value. (e.g. request_insecure_environment) +- lua_Debug info; ++ ++ // Coroutines start with an empty stack too, so we can't allow those. ++ bool coro = lua_pushthread(L) != 1; ++ lua_pop(L, 1); ++ if (coro) ++ return ""; + + // Make sure there's only one item below this function on the stack... ++ lua_Debug info; + if (lua_getstack(L, 2, &info)) + return ""; + FATAL_ERROR_IF(!lua_getstack(L, 1, &info), "lua_getstack() failed"); diff -Nru luanti-5.10.0+dfsg/debian/patches/series luanti-5.10.0+dfsg/debian/patches/series --- luanti-5.10.0+dfsg/debian/patches/series 2025-04-18 14:01:31.000000000 +0000 +++ luanti-5.10.0+dfsg/debian/patches/series 2026-04-15 17:50:05.000000000 +0000 @@ -1,3 +1,5 @@ Fix-rendering-regression-with-TGA-type-1-files-with-BGRA8.patch Fix-cross-build-failures.patch Add-CMake-flag-to-ignore-Lua-destructor-test-failure.patch +Lua-api-sanitisze-the-envionment-of-safe-functions.patch +fix-coroutine-confusion-with-ScriptApiSecurity-getCurrentModName.patch