File: 0010-CVE-2025-46818.patch

package info (click to toggle)
redis 5%3A8.0.2-3%2Bdeb13u1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 23,200 kB
  • sloc: ansic: 216,955; tcl: 51,683; sh: 4,625; perl: 4,214; cpp: 3,568; python: 2,954; makefile: 2,055; ruby: 639; javascript: 30; csh: 7
file content (253 lines) | stat: -rw-r--r-- 9,739 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
From 45eac0262028c771b6f5307372814b75f49f7a9e 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)

--- redis-8.0.2.orig/src/config.c
+++ redis-8.0.2/src/config.c
@@ -3114,6 +3114,7 @@ standardConfig static_configs[] = {
     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("hide-user-data-from-log", NULL, MODIFIABLE_CONFIG, server.hide_user_data_from_log, 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),
--- redis-8.0.2.orig/src/eval.c
+++ redis-8.0.2/src/eval.c
@@ -252,6 +252,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;
 }
--- redis-8.0.2.orig/src/function_lua.c
+++ redis-8.0.2/src/function_lua.c
@@ -495,6 +495,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) {
--- redis-8.0.2.orig/src/script_lua.c
+++ redis-8.0.2/src/script_lua.c
@@ -47,7 +47,6 @@ static char *redis_api_allow_list[] = {
 static char *lua_builtins_allow_list[] = {
     "xpcall",
     "tostring",
-    "getfenv",
     "setmetatable",
     "next",
     "assert",
@@ -68,15 +67,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,
 };
 
@@ -98,7 +98,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,
 };
@@ -1301,7 +1300,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;
@@ -1310,7 +1324,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 {
@@ -1362,6 +1376,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 */
+    }
+}
+
 void luaRegisterVersion(lua_State* lua) {
     lua_pushstring(lua,"REDIS_VERSION_NUM");
     lua_pushnumber(lua,REDIS_VERSION_NUM);
--- redis-8.0.2.orig/src/script_lua.h
+++ redis-8.0.2/src/script_lua.h
@@ -51,6 +51,7 @@ void luaRegisterGlobalProtectionFunction
 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);
--- redis-8.0.2.orig/src/server.h
+++ redis-8.0.2/src/server.h
@@ -2194,6 +2194,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 "noscript" inside a script. */
+    int lua_enable_deprecated_api;     /* Config to enable deprecated api */
     /* Lazy free */
     int lazyfree_lazy_eviction;
     int lazyfree_lazy_expire;
--- redis-8.0.2.orig/tests/unit/scripting.tcl
+++ redis-8.0.2/tests/unit/scripting.tcl
@@ -1078,6 +1078,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 {
@@ -1106,6 +1127,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"}} {