File CVE-2025-46818.patch of Package redis.41031
From de5e6aef1f95800bf0b58b33d8108d65c0f80ecd Mon Sep 17 00:00:00 2001
From: Ozan Tezcan <ozantezcan@gmail.com>
Date: Mon, 23 Jun 2025 12:10:12 +0300
Subject: [PATCH] Lua script can be executed in the context of another user
(CVE-2025-46818)
---
src/config.c | 1 +
src/scripting.c | 61 +++++++++++++++++++++++++++----
src/server.h | 1 +
tests/unit/introspection.tcl | 1 +
tests/unit/scripting.tcl | 70 ++++++++++++++++++++++++++++++++++++
5 files changed, 127 insertions(+), 7 deletions(-)
Index: b/src/config.c
===================================================================
--- a/src/config.c
+++ b/src/config.c
@@ -2440,6 +2440,7 @@ standardConfig configs[] = {
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL),
+ createBoolConfig("lua-enable-deprecated-api", NULL, IMMUTABLE_CONFIG, server.lua_enable_deprecated_api, 0, NULL, NULL),
/* String Configs */
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
Index: b/src/scripting.c
===================================================================
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -63,7 +63,6 @@ static char *redis_api_allow_list[] = {
static char *lua_builtins_allow_list[] = {
"xpcall",
"tostring",
- "getfenv",
"setmetatable",
"next",
"assert",
@@ -84,15 +83,16 @@ static char *lua_builtins_allow_list[] =
"loadstring",
"ipairs",
"_VERSION",
- "setfenv",
"load",
"error",
NULL,
};
-/* Lua builtins which are not documented on the Lua documentation */
-static char *lua_builtins_not_documented_allow_list[] = {
+/* Lua builtins which are deprecated for sandboxing concerns */
+static char *lua_builtins_deprecated[] = {
"newproxy",
+ "setfenv",
+ "getfenv",
NULL,
};
@@ -114,7 +114,6 @@ static char **allow_lists[] = {
libraries_allow_list,
redis_api_allow_list,
lua_builtins_allow_list,
- lua_builtins_not_documented_allow_list,
lua_builtins_removed_after_initialization_allow_list,
NULL,
};
@@ -1219,6 +1218,37 @@ void luaSetTableProtectionRecursively(lu
}
}
+/* Set the readonly flag on the metatable of basic types (string, nil etc.) */
+void luaSetTableProtectionForBasicTypes(lua_State *lua) {
+ static const int types[] = {
+ LUA_TSTRING,
+ LUA_TNUMBER,
+ LUA_TBOOLEAN,
+ LUA_TNIL,
+ LUA_TFUNCTION,
+ LUA_TTHREAD,
+ LUA_TLIGHTUSERDATA
+ };
+
+ for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
+ /* Push a dummy value of the type to get its metatable */
+ switch (types[i]) {
+ case LUA_TSTRING: lua_pushstring(lua, ""); break;
+ case LUA_TNUMBER: lua_pushnumber(lua, 0); break;
+ case LUA_TBOOLEAN: lua_pushboolean(lua, 0); break;
+ case LUA_TNIL: lua_pushnil(lua); break;
+ case LUA_TFUNCTION: lua_pushcfunction(lua, NULL); break;
+ case LUA_TTHREAD: lua_newthread(lua); break;
+ case LUA_TLIGHTUSERDATA: lua_pushlightuserdata(lua, (void*)lua); break;
+ }
+ if (lua_getmetatable(lua, -1)) {
+ luaSetTableProtectionRecursively(lua);
+ lua_pop(lua, 1); /* pop metatable */
+ }
+ lua_pop(lua, 1); /* pop dummy value */
+ }
+}
+
static int luaNewIndexAllowList(lua_State *lua) {
int argc = lua_gettop(lua);
if (argc != 3) {
@@ -1246,7 +1276,22 @@ static int luaNewIndexAllowList(lua_Stat
break;
}
}
- if (!*allow_l) {
+
+ int allowed = (*allow_l != NULL);
+ /* If not explicitly allowed, check if it's a deprecated function. If so,
+ * allow it only if 'lua_enable_deprecated_api' config is enabled. */
+ int deprecated = 0;
+ if (!allowed) {
+ char **c = lua_builtins_deprecated;
+ for (; *c; ++c) {
+ if (strcmp(*c, variable_name) == 0) {
+ deprecated = 1;
+ allowed = server.lua_enable_deprecated_api ? 1 : 0;
+ break;
+ }
+ }
+ }
+ if (!allowed) {
/* Search the value on the back list, if its there we know that it was removed
* on purpose and there is no need to print a warning. */
char **c = deny_list;
@@ -1255,7 +1300,7 @@ static int luaNewIndexAllowList(lua_Stat
break;
}
}
- if (!*c) {
+ if (!*c && !deprecated) {
serverLog(LL_WARNING, "A key '%s' was added to Lua globals which is not on the globals allow list nor listed on the deny list.", variable_name);
}
} else {
@@ -1470,6 +1515,8 @@ void scriptingInit(int setup) {
/* Recursively lock all tables that can be reached from the global table */
luaSetTableProtectionRecursively(lua);
lua_pop(lua, 1);
+ /* Set metatables of basic types (string, number, nil etc.) readonly. */
+ luaSetTableProtectionForBasicTypes(lua);
server.lua = lua;
}
Index: b/src/server.h
===================================================================
--- a/src/server.h
+++ b/src/server.h
@@ -1590,6 +1590,7 @@ struct redisServer {
int lua_kill; /* Kill the script if true. */
int lua_always_replicate_commands; /* Default replication type. */
int lua_oom; /* OOM detected when script start? */
+ int lua_enable_deprecated_api; /* Config to enable deprecated api */
/* Lazy free */
int lazyfree_lazy_eviction;
int lazyfree_lazy_expire;
Index: b/tests/unit/introspection.tcl
===================================================================
--- a/tests/unit/introspection.tcl
+++ b/tests/unit/introspection.tcl
@@ -156,5 +156,6 @@ start_server {tags {"introspection"}} {
aof_rewrite_cpulist
bgsave_cpulist
+ lua-enable-deprecated-api
}
if {!$::tls} {
Index: b/tests/unit/scripting.tcl
===================================================================
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -798,6 +798,27 @@ start_server {tags {"scripting"}} {
set _ $e
} {*Attempt to modify a readonly table*}
+ test "Try trick readonly table on basic types metatable" {
+ # Run the following scripts for basic types. Either getmetatable()
+ # should return nil or the metatable must be readonly.
+ set scripts {
+ {getmetatable(nil).__index = function() return 1 end}
+ {getmetatable('').__index = function() return 1 end}
+ {getmetatable(123.222).__index = function() return 1 end}
+ {getmetatable(true).__index = function() return 1 end}
+ {getmetatable(function() return 1 end).__index = function() return 1 end}
+ {getmetatable(coroutine.create(function() return 1 end)).__index = function() return 1 end}
+ }
+
+ foreach code $scripts {
+ catch {r eval $code 0} e
+ assert {
+ [string match "*attempt to index a nil value*" $e] ||
+ [string match "*Attempt to modify a readonly table*" $e]
+ }
+ }
+ }
+
test "Test loadfile are not available" {
catch {
r eval {
@@ -826,6 +847,55 @@ start_server {tags {"scripting"}} {
} {*Script attempted to access nonexistent global variable 'print'*}
}
+# Start a new server to test lua-enable-deprecated-api config
+foreach enabled {no yes} {
+start_server [subst {tags {"scripting external:skip"} overrides {lua-enable-deprecated-api $enabled}}] {
+ test "Test setfenv availability lua-enable-deprecated-api=$enabled" {
+ catch {
+ r eval {
+ local f = function() return 1 end
+ setfenv(f, {})
+ return 0
+ } 0
+ } e
+ if {$enabled} {
+ assert_equal $e 0
+ } else {
+ assert_match {*Script attempted to access nonexistent global variable 'setfenv'*} $e
+ }
+ }
+
+ test "Test getfenv availability lua-enable-deprecated-api=$enabled" {
+ catch {
+ r eval {
+ local f = function() return 1 end
+ getfenv(f)
+ return 0
+ } 0
+ } e
+ if {$enabled} {
+ assert_equal $e 0
+ } else {
+ assert_match {*Script attempted to access nonexistent global variable 'getfenv'*} $e
+ }
+ }
+
+ test "Test newproxy availability lua-enable-deprecated-api=$enabled" {
+ catch {
+ r eval {
+ getmetatable(newproxy(true)).__gc = function() return 1 end
+ return 0
+ } 0
+ } e
+ if {$enabled} {
+ assert_equal $e 0
+ } else {
+ assert_match {*Script attempted to access nonexistent global variable 'newproxy'*} $e
+ }
+ }
+}
+}
+
# Start a new server since the last test in this stanza will kill the
# instance at all.
start_server {tags {"scripting"}} {