File CVE-2025-46819.patch of Package redis.41031
From ef22554057e50c67d0f8d0ede39483358356321f Mon Sep 17 00:00:00 2001
From: Ozan Tezcan <ozantezcan@gmail.com>
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(-)
Index: b/deps/lua/src/llex.c
===================================================================
--- 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,
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 *
}
} 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 *
/* 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 *
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);
Index: b/tests/test_helper.tcl
===================================================================
--- a/tests/test_helper.tcl
+++ b/tests/test_helper.tcl
@@ -107,6 +107,7 @@ set ::wait_server 0
set ::stop_on_failure 0
set ::loop 0
set ::tlsdir "tests/tls"
+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
@@ -616,6 +617,8 @@ for {set j 0} {$j < [llength $argv]} {in
} 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
Index: b/tests/unit/scripting.tcl
===================================================================
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -809,6 +809,45 @@ 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 - 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}}] {