From b0feb55bb6e390a2ed3f786446f7e767bc935c4e Mon Sep 17 00:00:00 2001 From: wh02252983 Date: Fri, 10 Oct 2025 15:18:55 +0800 Subject: [PATCH] [CVE] add patch to fix CVE-2025-46818 and CVE-2025-46819 To # N/A add patch to fix CVE-2025-46818 and CVE-2025-46819 Project: TC2024080204 Signed-off-by: wh02252983 --- 0003-Fix-CVE-2025-46818.patch | 264 ++++++++++++++++++++++++++++++++++ 0004-Fix-CVE-2025-46819.patch | 160 +++++++++++++++++++++ 0005-Fix-CVE-2025-46817.patch | 89 ++++++++++++ redis.spec | 14 +- 4 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 0003-Fix-CVE-2025-46818.patch create mode 100644 0004-Fix-CVE-2025-46819.patch create mode 100644 0005-Fix-CVE-2025-46817.patch diff --git a/0003-Fix-CVE-2025-46818.patch b/0003-Fix-CVE-2025-46818.patch new file mode 100644 index 0000000..4477d5b --- /dev/null +++ b/0003-Fix-CVE-2025-46818.patch @@ -0,0 +1,264 @@ +diff --git a/src/config.c b/src/config.c +index 9e8c5ba..cde2dd6 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -3097,6 +3097,7 @@ standardConfig static_configs[] = { + createBoolConfig("latency-tracking", NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL), + createBoolConfig("aof-disable-auto-gc", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled), + createBoolConfig("replica-ignore-disk-write-errors", NULL, MODIFIABLE_CONFIG, server.repl_ignore_disk_write_error, 0, NULL, NULL), ++ createBoolConfig("lua-enable-deprecated-api", NULL, IMMUTABLE_CONFIG | HIDDEN_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/eval.c b/src/eval.c +index 47c66b6..6d22533 100644 +--- a/src/eval.c ++++ b/src/eval.c +@@ -260,6 +260,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); + + lctx.lua = lua; + } +diff --git a/src/function_lua.c b/src/function_lua.c +index be79dc1..dacd534 100644 +--- a/src/function_lua.c ++++ b/src/function_lua.c +@@ -491,6 +491,8 @@ int luaEngineInitEngine(void) { + lua_enablereadonlytable(lua_engine_ctx->lua, -1, 1); /* protect the new global table */ + lua_replace(lua_engine_ctx->lua, LUA_GLOBALSINDEX); /* set new global table as the new globals */ + ++ /* Set metatables of basic types (string, number, nil etc.) readonly. */ ++ luaSetTableProtectionForBasicTypes(lua_engine_ctx->lua); + + engine *lua_engine = zmalloc(sizeof(*lua_engine)); + *lua_engine = (engine) { +diff --git a/src/script_lua.c b/src/script_lua.c +index 2c92638..8b64e62 100644 +--- a/src/script_lua.c ++++ b/src/script_lua.c +@@ -66,7 +66,6 @@ static char *redis_api_allow_list[] = { + static char *lua_builtins_allow_list[] = { + "xpcall", + "tostring", +- "getfenv", + "setmetatable", + "next", + "assert", +@@ -87,15 +86,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, + }; + +@@ -117,7 +117,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, + }; +@@ -1320,7 +1319,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; +@@ -1329,7 +1343,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 { +@@ -1381,6 +1395,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 */ ++ } ++} ++ + void luaRegisterVersion(lua_State* lua) { + lua_pushstring(lua,"REDIS_VERSION_NUM"); + lua_pushnumber(lua,REDIS_VERSION_NUM); +diff --git a/src/script_lua.h b/src/script_lua.h +index 4c2b348..d8a3688 100644 +--- a/src/script_lua.h ++++ b/src/script_lua.h +@@ -71,6 +71,7 @@ void luaRegisterGlobalProtectionFunction(lua_State *lua); + void luaSetErrorMetatable(lua_State *lua); + void luaSetAllowListProtection(lua_State *lua); + void luaSetTableProtectionRecursively(lua_State *lua); ++void luaSetTableProtectionForBasicTypes(lua_State *lua); + void luaRegisterLogFunction(lua_State* lua); + void luaRegisterVersion(lua_State* lua); + void luaPushErrorBuff(lua_State *lua, sds err_buff); +diff --git a/src/server.h b/src/server.h +index 586711e..defcaf1 100644 +--- a/src/server.h ++++ b/src/server.h +@@ -2002,6 +2002,7 @@ struct redisServer { + mstime_t busy_reply_threshold; /* Script / module timeout in milliseconds */ + int pre_command_oom_state; /* OOM before command (script?) was started */ + int script_disable_deny_script; /* Allow running commands marked "no-script" inside a script. */ ++ 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/scripting.tcl b/tests/unit/scripting.tcl +index 635076b..fa084b2 100644 +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -1007,6 +1007,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 {run_script $code 0} e ++ assert { ++ [string match "*attempt to index a nil value script*" $e] || ++ [string match "*Attempt to modify a readonly table*" $e] ++ } ++ } ++ } ++ + test "Test loadfile are not available" { + catch { + run_script { +@@ -1035,6 +1056,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 { ++ run_script { ++ 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 { ++ run_script { ++ 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 { ++ run_script { ++ 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.43.5 + diff --git a/0004-Fix-CVE-2025-46819.patch b/0004-Fix-CVE-2025-46819.patch new file mode 100644 index 0000000..2d566da --- /dev/null +++ b/0004-Fix-CVE-2025-46819.patch @@ -0,0 +1,160 @@ +diff --git a/deps/lua/src/llex.c b/deps/lua/src/llex.c +index 88c6790..1e49c04 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 */ +@@ -207,8 +208,13 @@ static void read_numeral (LexState *ls, SemInfo *seminfo) { + } + + +-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 +222,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 +278,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 +354,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 +368,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/unit/scripting.tcl b/tests/unit/scripting.tcl +index fa084b2..b0ed8a6 100644 +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -315,13 +315,6 @@ start_server {tags {"scripting"}} { + set e + } {*against a key*} + +- test {EVAL - JSON string encoding a string larger than 2GB} { +- run_script { +- local s = string.rep("a", 1024 * 1024 * 1024) +- return #cjson.encode(s..s..s) +- } 0 +- } {3221225474} {large-memory} ;# length includes two double quotes at both ends +- + test {EVAL - JSON numeric decoding} { + # We must return the table as a string because otherwise + # Redis converts floats to ints and we get 0 and 1023 instead +@@ -1056,6 +1049,52 @@ start_server {tags {"scripting"}} { + } {*Script attempted to access nonexistent global variable 'print'*} + } + ++# start a new server to test the large-memory tests ++start_server {tags {"scripting external:skip large-memory"}} { ++ test {EVAL - JSON string encoding a string larger than 2GB} { ++ run_script { ++ local s = string.rep("a", 1024 * 1024 * 1024) ++ return #cjson.encode(s..s..s) ++ } 0 ++ } {3221225474} ;# length includes two double quotes at both ends ++ ++ test {EVAL - Test long escape sequences for strings} { ++ run_script { ++ -- 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. ++ ++ run_script { ++ 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.43.5 + diff --git a/0005-Fix-CVE-2025-46817.patch b/0005-Fix-CVE-2025-46817.patch new file mode 100644 index 0000000..90a0f72 --- /dev/null +++ b/0005-Fix-CVE-2025-46817.patch @@ -0,0 +1,89 @@ +diff --git a/deps/lua/src/lbaselib.c b/deps/lua/src/lbaselib.c +index 2ab550b..26172d1 100644 +--- a/deps/lua/src/lbaselib.c ++++ b/deps/lua/src/lbaselib.c +@@ -340,13 +340,14 @@ static int luaB_assert (lua_State *L) { + + + static int luaB_unpack (lua_State *L) { +- int i, e, n; ++ int i, e; ++ unsigned int n; + luaL_checktype(L, 1, LUA_TTABLE); + i = luaL_optint(L, 2, 1); + e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1)); + if (i > e) return 0; /* empty range */ +- n = e - i + 1; /* number of elements */ +- if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ ++ n = (unsigned int)e - (unsigned int)i; /* number of elements minus 1 */ ++ if (n >= INT_MAX || !lua_checkstack(L, ++n)) + return luaL_error(L, "too many results to unpack"); + lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ + while (i++ < e) /* push arg[i + 1...e] */ +diff --git a/deps/lua/src/ltable.c b/deps/lua/src/ltable.c +index f75fe19..55575a8 100644 +--- a/deps/lua/src/ltable.c ++++ b/deps/lua/src/ltable.c +@@ -434,8 +434,7 @@ static TValue *newkey (lua_State *L, Table *t, const TValue *key) { + ** search function for integers + */ + const TValue *luaH_getnum (Table *t, int key) { +- /* (1 <= key && key <= t->sizearray) */ +- if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) ++ if (1 <= key && key <= t->sizearray) + return &t->array[key-1]; + else { + lua_Number nk = cast_num(key); +diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl +index b0ed8a6..9f72451 100644 +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -315,6 +315,45 @@ start_server {tags {"scripting"}} { + set e + } {*against a key*} + ++ test {EVAL - Test table unpack with invalid indexes} { ++ catch {run_script { return {unpack({1,2,3}, -2, 2147483647)} } 0} e ++ assert_match {*too many results to unpack*} $e ++ catch {run_script { return {unpack({1,2,3}, 0, 2147483647)} } 0} e ++ assert_match {*too many results to unpack*} $e ++ catch {run_script { return {unpack({1,2,3}, -2147483648, -2)} } 0} e ++ assert_match {*too many results to unpack*} $e ++ set res [run_script { return {unpack({1,2,3}, -1, -2)} } 0] ++ assert_match {} $res ++ set res [run_script { return {unpack({1,2,3}, 1, -1)} } 0] ++ assert_match {} $res ++ ++ # unpack with range -1 to 5, verify nil indexes ++ set res [run_script { ++ local function unpack_to_list(t, i, j) ++ local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)} ++ for i = 1, n do v[i] = v[i] or '_NIL_' end ++ v.n = n ++ return v ++ end ++ ++ return unpack_to_list({1,2,3}, -1, 5) ++ } 0] ++ assert_match {_NIL_ _NIL_ 1 2 3 _NIL_ _NIL_} $res ++ ++ # unpack with negative range, verify nil indexes ++ set res [run_script { ++ local function unpack_to_list(t, i, j) ++ local n, v = select('#', unpack(t, i, j)), {unpack(t, i, j)} ++ for i = 1, n do v[i] = v[i] or '_NIL_' end ++ v.n = n ++ return v ++ end ++ ++ return unpack_to_list({1,2,3}, -2147483648, -2147483646) ++ } 0] ++ assert_match {_NIL_ _NIL_ _NIL_} $res ++ } {} ++ + test {EVAL - JSON numeric decoding} { + # We must return the table as a string because otherwise + # Redis converts floats to ints and we get 0 and 1023 instead +-- +2.43.5 + diff --git a/redis.spec b/redis.spec index 1a09692..cb343ee 100644 --- a/redis.spec +++ b/redis.spec @@ -1,4 +1,4 @@ -%define anolis_release 1 +%define anolis_release 2 # temp workaround to https://bugzilla.redhat.com/2059488 %undefine _package_note_file @@ -30,6 +30,12 @@ Source10: https://github.com/%{name}/%{name}-doc/archive/%{doc_commit}/ Patch0001: 0001-1st-man-pageis-for-redis-cli-redis-benchmark-redis-c.patch Patch0002: 0002-add-sw_64-support.patch +# https://github.com/redis/redis/commit/45eac0262028c771b6f5307372814b75f49f7a9e +Patch0003: 0003-Fix-CVE-2025-46818.patch +# https://github.com/redis/redis/commit/3a1624da2449ac3dbfc4bdaed43adf77a0b7bfba +Patch0004: 0004-Fix-CVE-2025-46819.patch +# https://github.com/redis/redis/commit/fc9abc775e308374f667fdf3e723ef4b7eb0e3ca +Patch0005: 0005-Fix-CVE-2025-46817.patch BuildRequires: make BuildRequires: gcc @@ -115,6 +121,9 @@ administration and development. mv ../%{name}-doc-%{doc_commit} doc %patch -P0001 -p1 %patch -P0002 -p1 +%patch -P0003 -p1 +%patch -P0004 -p1 +%patch -P0005 -p1 mv deps/lua/COPYRIGHT COPYRIGHT-lua mv deps/jemalloc/COPYING COPYING-jemalloc @@ -288,6 +297,9 @@ fi %changelog +* Fri Oct 10 2025 wh02252983 - 7.2.10-2 +- Add patch to fix CVE-2025-46818 and CVE-2025-46819 and CVE-2025-46817 + * Fri Aug 29 2025 wenxin - 7.2.10-1 - Update to 7.2.10 to fix CVE-2025-27151 - Removed redundant patches included in this version -- Gitee