From 71ca527588455e44b6ddf1943fba26a238f0a21e Mon Sep 17 00:00:00 2001 From: jxy_git Date: Thu, 4 Dec 2025 15:21:51 +0800 Subject: [PATCH] Fix CVE-2025-46818 CVE-2025-46819 (cherry picked from commit 2a4523f9546506c2ebcd56248614570a1c7d7099) --- backport-CVE-2025-46818.patch | 262 ++++++++++++++++++++++++++++++++++ backport-CVE-2025-46819.patch | 189 ++++++++++++++++++++++++ redis6.spec | 9 +- 3 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2025-46818.patch create mode 100644 backport-CVE-2025-46819.patch diff --git a/backport-CVE-2025-46818.patch b/backport-CVE-2025-46818.patch new file mode 100644 index 0000000..6f8ceea --- /dev/null +++ b/backport-CVE-2025-46818.patch @@ -0,0 +1,262 @@ +From de5e6aef1f95800bf0b58b33d8108d65c0f80ecd Mon Sep 17 00:00:00 2001 +From: Ozan Tezcan +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(-) + +diff --git a/src/config.c b/src/config.c +index 691b8a045..c2fdb8747 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -2440,6 +2440,7 @@ standardConfig configs[] = { + createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, server.disable_thp, 1, NULL, NULL), + createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL), + createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, 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), +diff --git a/src/scripting.c b/src/scripting.c +index 656d4dd16..27a38c4f5 100644 +--- 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,16 +83,17 @@ static char *lua_builtins_allow_list[] = { + "loadstring", + "ipairs", + "_VERSION", +- "setfenv", + "load", + "error", + "print", /* should be blocked, but kept in old releases to avoid breaking change. */ + 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, + }; + +@@ -115,7 +115,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, + }; +@@ -1216,6 +1215,37 @@ void luaSetTableProtectionRecursively(lua_State *lua) { + } + } + ++/* 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) { +@@ -1243,7 +1273,22 @@ static int luaNewIndexAllowList(lua_State *lua) { + 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; +@@ -1252,7 +1297,7 @@ static int luaNewIndexAllowList(lua_State *lua) { + 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 { +@@ -1455,6 +1500,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; + } +diff --git a/src/server.h b/src/server.h +index 7afd6843e..4d99f00ce 100644 +--- a/src/server.h ++++ b/src/server.h +@@ -1598,6 +1598,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; +diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl +index b1319211e..490f3d4c2 100644 +--- a/tests/unit/introspection.tcl ++++ b/tests/unit/introspection.tcl +@@ -198,6 +198,7 @@ start_server {tags {"introspection"}} { + aof_rewrite_cpulist + bgsave_cpulist + set-proc-title ++ lua-enable-deprecated-api + } + + if {!$::tls} { +diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl +index 2381927cd..219b075b2 100644 +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -812,6 +812,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 { +@@ -866,6 +887,55 @@ start_server {tags {"scripting"}} { + } {7999} + } + ++# 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"}} { +-- +2.51.0 + diff --git a/backport-CVE-2025-46819.patch b/backport-CVE-2025-46819.patch new file mode 100644 index 0000000..ca7f52d --- /dev/null +++ b/backport-CVE-2025-46819.patch @@ -0,0 +1,189 @@ +From ef22554057e50c67d0f8d0ede39483358356321f Mon Sep 17 00:00:00 2001 +From: Ozan Tezcan +Date: Mon, 23 Jun 2025 12:11:31 +0300 +Subject: [PATCH] LUA out-of-bound read (CVE-2025-46819) + +--- + deps/lua/src/llex.c | 34 +++++++++++++++++++++------------- + tests/support/server.tcl | 5 +++++ + tests/test_helper.tcl | 3 +++ + tests/unit/scripting.tcl | 39 +++++++++++++++++++++++++++++++++++++++ + 4 files changed, 68 insertions(+), 13 deletions(-) + +diff --git a/deps/lua/src/llex.c b/deps/lua/src/llex.c +index 88c6790c0..efad70922 100644 +--- a/deps/lua/src/llex.c ++++ b/deps/lua/src/llex.c +@@ -138,6 +138,7 @@ static void inclinenumber (LexState *ls) { + + + void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) { ++ ls->t.token = 0; + ls->decpoint = '.'; + ls->L = L; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ +@@ -206,9 +207,13 @@ static void read_numeral (LexState *ls, SemInfo *seminfo) { + trydecpoint(ls, seminfo); /* try to update decimal point separator */ + } + +- +-static int skip_sep (LexState *ls) { +- int count = 0; ++/* ++** reads a sequence '[=*[' or ']=*]', leaving the last bracket. ++** If a sequence is well-formed, return its number of '='s + 2; otherwise, ++** return 1 if there is no '='s or 0 otherwise (an unfinished '[==...'). ++*/ ++static size_t skip_sep (LexState *ls) { ++ size_t count = 0; + int s = ls->current; + lua_assert(s == '[' || s == ']'); + save_and_next(ls); +@@ -216,11 +221,13 @@ static int skip_sep (LexState *ls) { + save_and_next(ls); + count++; + } +- return (ls->current == s) ? count : (-count) - 1; ++ return (ls->current == s) ? count + 2 ++ : (count == 0) ? 1 ++ : 0; + } + + +-static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { ++static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) { + int cont = 0; + (void)(cont); /* avoid warnings when `cont' is not used */ + save_and_next(ls); /* skip 2nd `[' */ +@@ -270,8 +277,8 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { + } + } endloop: + if (seminfo) +- seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), +- luaZ_bufflen(ls->buff) - 2*(2 + sep)); ++ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep, ++ luaZ_bufflen(ls->buff) - 2 * sep); + } + + +@@ -346,9 +353,9 @@ static int llex (LexState *ls, SemInfo *seminfo) { + /* else is a comment */ + next(ls); + if (ls->current == '[') { +- int sep = skip_sep(ls); ++ size_t sep = skip_sep(ls); + luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ +- if (sep >= 0) { ++ if (sep >= 2) { + read_long_string(ls, NULL, sep); /* long comment */ + luaZ_resetbuffer(ls->buff); + continue; +@@ -360,13 +367,14 @@ static int llex (LexState *ls, SemInfo *seminfo) { + continue; + } + case '[': { +- int sep = skip_sep(ls); +- if (sep >= 0) { ++ size_t sep = skip_sep(ls); ++ if (sep >= 2) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } +- else if (sep == -1) return '['; +- else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING); ++ else if (sep == 0) /* '[=...' missing second bracket */ ++ luaX_lexerror(ls, "invalid long string delimiter", TK_STRING); ++ return '['; + } + case '=': { + next(ls); +diff --git a/tests/support/server.tcl b/tests/support/server.tcl +index 340c5dc4c..45301c55c 100644 +--- a/tests/support/server.tcl ++++ b/tests/support/server.tcl +@@ -198,6 +198,11 @@ proc tags_acceptable {err_return} { + return 0 + } + ++ if {!$::large_memory && [lsearch $tags "large-memory"] >= 0} { ++ set err "large memory flag not provided" ++ return 0 ++ } ++ + return 1 + } + +diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl +index 5a4cbd49f..10f69f2a6 100644 +--- a/tests/test_helper.tcl ++++ b/tests/test_helper.tcl +@@ -117,6 +117,7 @@ set ::dump_logs 0 + set ::loop 0 + set ::tlsdir "tests/tls" + set ::singledb 0 ++set ::large_memory 0 + + # Set to 1 when we are running in client mode. The Redis test uses a + # server-client model to run tests simultaneously. The server instance +@@ -644,6 +645,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} { + } elseif {$opt eq {--single}} { + lappend ::single_tests $arg + incr j ++ } elseif {$opt eq {--large-memory}} { ++ set ::large_memory 1 + } elseif {$opt eq {--only}} { + lappend ::only_tests $arg + incr j +diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl +index 219b075b2..be50c00d2 100644 +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -887,6 +887,45 @@ start_server {tags {"scripting"}} { + } {7999} + } + ++# start a new server to test the large-memory tests ++start_server {tags {"scripting external:skip large-memory"}} { ++ test {EVAL - Test long escape sequences for strings} { ++ r eval { ++ -- Generate 1gb '==...==' separator ++ local s = string.rep('=', 1024 * 1024) ++ local t = {} for i=1,1024 do t[i] = s end ++ local sep = table.concat(t) ++ collectgarbage('collect') ++ ++ local code = table.concat({'return [',sep,'[x]',sep,']'}) ++ collectgarbage('collect') ++ ++ -- Load the code and run it. Script will return the string length. ++ -- Escape sequence: [=....=[ to ]=...=] will be ignored ++ -- Actual string is a single character: 'x'. Script will return 1 ++ local func = loadstring(code) ++ return #func() ++ } 0 ++ } {1} ++ ++ test {EVAL - Lua can parse string with too many new lines} { ++ # Create a long string consisting only of newline characters. When Lua ++ # fails to parse a string, it typically includes a snippet like ++ # "... near ..." in the error message to indicate the last recognizable ++ # token. In this test, since the input contains only newlines, there ++ # should be no identifiable token, so the error message should contain ++ # only the actual error, without a near clause. ++ ++ r eval { ++ local s = string.rep('\n', 1024 * 1024) ++ local t = {} for i=1,2048 do t[#t+1] = s end ++ local lines = table.concat(t) ++ local fn, err = loadstring(lines) ++ return err ++ } 0 ++ } {*chunk has too many lines} ++} ++ + # 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}}] { +-- +2.51.0 + diff --git a/redis6.spec b/redis6.spec index 9fc1b60..ba6922d 100644 --- a/redis6.spec +++ b/redis6.spec @@ -6,7 +6,7 @@ %global Pname redis Name: redis6 Version: 6.2.7 -Release: 6 +Release: 7 Summary: A persistent key-value database License: BSD and MIT URL: https://redis.io @@ -30,6 +30,8 @@ Patch3001: backport-CVE-2025-32023.patch Patch3002: backport-CVE-2025-48367.patch Patch3003: backport-CVE-2025-49844.patch Patch3004: backport-CVE-2025-46817.patch +Patch3005: backport-CVE-2025-46818.patch +Patch3006: backport-CVE-2025-46819.patch BuildRequires: make gcc %if %{with tests} @@ -103,6 +105,8 @@ tar -xvf %{SOURCE10} %patch 3002 -p1 %patch 3003 -p1 %patch 3004 -p1 +%patch 3005 -p1 +%patch 3006 -p1 mv ../%{Pname}-doc-%{doc_commit} doc mv deps/lua/COPYRIGHT COPYRIGHT-lua @@ -232,6 +236,9 @@ fi %{_docdir}/%{Pname} %changelog +* Thu Dec 04 2025 jiangxinyu - 6.2.7-7 +- Fix CVE-2025-46818 CVE-2025-46819 + * Thu Dec 04 2025 jiangxinyu - 6.2.7-6 - Fix CVE-2025-21605 CVE-2025-32023 CVE-2025-48367 CVE-2025-49844 -- Gitee