121 lines
5.1 KiB
Diff
121 lines
5.1 KiB
Diff
Backported for 5.0.3
|
||
|
||
|
||
|
||
From a4b813d8b844094fcd77c511af596866043b20c8 Mon Sep 17 00:00:00 2001
|
||
From: "meir@redislabs.com" <meir@redislabs.com>
|
||
Date: Sun, 13 Jun 2021 14:27:18 +0300
|
||
Subject: [PATCH] Fix invalid memory write on lua stack overflow
|
||
{CVE-2021-32626}
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
When LUA call our C code, by default, the LUA stack has room for 20
|
||
elements. In most cases, this is more than enough but sometimes it's not
|
||
and the caller must verify the LUA stack size before he pushes elements.
|
||
|
||
On 3 places in the code, there was no verification of the LUA stack size.
|
||
On specific inputs this missing verification could have lead to invalid
|
||
memory write:
|
||
1. On 'luaReplyToRedisReply', one might return a nested reply that will
|
||
explode the LUA stack.
|
||
2. On 'redisProtocolToLuaType', the Redis reply might be deep enough
|
||
to explode the LUA stack (notice that currently there is no such
|
||
command in Redis that returns such a nested reply, but modules might
|
||
do it)
|
||
3. On 'ldbRedis', one might give a command with enough arguments to
|
||
explode the LUA stack (all the arguments will be pushed to the LUA
|
||
stack)
|
||
|
||
This commit is solving all those 3 issues by calling 'lua_checkstack' and
|
||
verify that there is enough room in the LUA stack to push elements. In
|
||
case 'lua_checkstack' returns an error (there is not enough room in the
|
||
LUA stack and it's not possible to increase the stack), we will do the
|
||
following:
|
||
1. On 'luaReplyToRedisReply', we will return an error to the user.
|
||
2. On 'redisProtocolToLuaType' we will exit with panic (we assume this
|
||
scenario is rare because it can only happen with a module).
|
||
3. On 'ldbRedis', we return an error.
|
||
|
||
(cherry picked from commit d32a3f74f2a343846b50920e95754a955c1a10a9)
|
||
---
|
||
src/scripting.c | 36 ++++++++++++++++++++++++++++++++++++
|
||
1 file changed, 36 insertions(+)
|
||
|
||
diff --git a/src/scripting.c b/src/scripting.c
|
||
index db1e4d4b5f1..153b942404e 100644
|
||
--- a/src/scripting.c
|
||
+++ b/src/scripting.c
|
||
@@ -125,6 +125,16 @@ void sha1hex(char *digest, char *script, size_t len) {
|
||
*/
|
||
|
||
char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||
+
|
||
+ if (!lua_checkstack(lua, 5)) {
|
||
+ /*
|
||
+ * Increase the Lua stack if needed, to make sure there is enough room
|
||
+ * to push 5 elements to the stack. On failure, exit with panic.
|
||
+ * Notice that we need, in the worst case, 5 elements because redisProtocolToLuaType_Aggregate
|
||
+ * might push 5 elements to the Lua stack.*/
|
||
+ serverPanic("lua stack limit reach when parsing redis.call reply");
|
||
+ }
|
||
+
|
||
char *p = reply;
|
||
|
||
switch(*p) {
|
||
@@ -275,6 +285,17 @@ void luaSortArray(lua_State *lua) {
|
||
* ------------------------------------------------------------------------- */
|
||
|
||
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||
+
|
||
+ if (!lua_checkstack(lua, 4)) {
|
||
+ /* Increase the Lua stack if needed to make sure there is enough room
|
||
+ * to push 4 elements to the stack. On failure, return error.
|
||
+ * Notice that we need, in the worst case, 4 elements because returning a map might
|
||
+ * require push 4 elements to the Lua stack.*/
|
||
+ addReplyErrorFormat(c, "reached lua stack limit");
|
||
+ lua_pop(lua,1); // pop the element from the stack
|
||
+ return;
|
||
+ }
|
||
+
|
||
int t = lua_type(lua,-1);
|
||
|
||
switch(t) {
|
||
@@ -292,6 +313,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||
* Error are returned as a single element table with 'err' field.
|
||
* Status replies are returned as single element table with 'ok'
|
||
* field. */
|
||
+
|
||
+ /* Handle error reply. */
|
||
+ /* we took care of the stack size on function start */
|
||
lua_pushstring(lua,"err");
|
||
lua_gettable(lua,-2);
|
||
t = lua_type(lua,-1);
|
||
@@ -320,6 +344,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||
|
||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
||
while(1) {
|
||
+ /* we took care of the stack size on function start */
|
||
lua_pushnumber(lua,j++);
|
||
lua_gettable(lua,-2);
|
||
t = lua_type(lua,-1);
|
||
@@ -2231,6 +2256,17 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||
void ldbRedis(lua_State *lua, sds *argv, int argc) {
|
||
int j, saved_rc = server.lua_replicate_commands;
|
||
|
||
+ if (!lua_checkstack(lua, argc + 1)) {
|
||
+ /* Increase the Lua stack if needed to make sure there is enough room
|
||
+ * to push 'argc + 1' elements to the stack. On failure, return error.
|
||
+ * Notice that we need, in worst case, 'argc + 1' elements because we push all the arguments
|
||
+ * given by the user (without the first argument) and we also push the 'redis' global table and
|
||
+ * 'redis.call' function so:
|
||
+ * (1 (redis table)) + (1 (redis.call function)) + (argc - 1 (all arguments without the first)) = argc + 1*/
|
||
+ ldbLogRedisReply("max lua stack reached");
|
||
+ return;
|
||
+ }
|
||
+
|
||
lua_getglobal(lua,"redis");
|
||
lua_pushstring(lua,"call");
|
||
lua_gettable(lua,-2); /* Stack: redis, redis.call */
|