import redis-6.0.9-5.module+el8.4.0+12929+1bb0d2aa
This commit is contained in:
parent
ab1b607054
commit
bd1f46c8f2
140
SOURCES/redis-CVE-2021-32626.patch
Normal file
140
SOURCES/redis-CVE-2021-32626.patch
Normal file
@ -0,0 +1,140 @@
|
||||
From 666ed7facf4524bf6d19b11b20faa2cf93fdf591 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.
|
||||
---
|
||||
src/scripting.c | 41 +++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 41 insertions(+)
|
||||
|
||||
diff --git a/src/scripting.c b/src/scripting.c
|
||||
index dea5f516561..afa6adb0c47 100644
|
||||
--- a/src/scripting.c
|
||||
+++ b/src/scripting.c
|
||||
@@ -128,6 +128,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) {
|
||||
@@ -220,6 +230,11 @@ char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
|
||||
if (atype == '%') {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
} else {
|
||||
+ if (!lua_checkstack(lua, 1)) {
|
||||
+ /* Notice that here we need to check the stack again because the recursive
|
||||
+ * call to redisProtocolToLuaType might have use the room allocated in the stack */
|
||||
+ serverPanic("lua stack limit reach when parsing redis.call reply");
|
||||
+ }
|
||||
lua_pushboolean(lua,1);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
@@ -339,6 +354,17 @@ void luaSortArray(lua_State *lua) {
|
||||
/* Reply to client 'c' converting the top element in the Lua stack to a
|
||||
* Redis reply. As a side effect the element is consumed from the stack. */
|
||||
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) {
|
||||
@@ -362,6 +388,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
* 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);
|
||||
@@ -407,6 +434,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
if (t == LUA_TTABLE) {
|
||||
int maplen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
+ /* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, value */
|
||||
@@ -429,6 +457,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
if (t == LUA_TTABLE) {
|
||||
int setlen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
+ /* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, true */
|
||||
@@ -448,6 +477,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
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);
|
||||
@@ -2506,6 +2536,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 */
|
781
SOURCES/redis-CVE-2021-32627.patch
Normal file
781
SOURCES/redis-CVE-2021-32627.patch
Normal file
@ -0,0 +1,781 @@
|
||||
From f6a40570fa63d5afdd596c78083d754081d80ae3 Mon Sep 17 00:00:00 2001
|
||||
From: Oran Agra <oran@redislabs.com>
|
||||
Date: Thu, 3 Jun 2021 12:10:02 +0300
|
||||
Subject: [PATCH] Fix ziplist and listpack overflows and truncations
|
||||
(CVE-2021-32627, CVE-2021-32628)
|
||||
|
||||
- fix possible heap corruption in ziplist and listpack resulting by trying to
|
||||
allocate more than the maximum size of 4GB.
|
||||
- prevent ziplist (hash and zset) from reaching size of above 1GB, will be
|
||||
converted to HT encoding, that's not a useful size.
|
||||
- prevent listpack (stream) from reaching size of above 1GB.
|
||||
- XADD will start a new listpack if the new record may cause the previous
|
||||
listpack to grow over 1GB.
|
||||
- XADD will respond with an error if a single stream record is over 1GB
|
||||
- List type (ziplist in quicklist) was truncating strings that were over 4GB,
|
||||
now it'll respond with an error.
|
||||
---
|
||||
src/geo.c | 5 +-
|
||||
src/listpack.c | 2 +-
|
||||
src/quicklist.c | 17 ++++-
|
||||
src/rdb.c | 36 ++++++---
|
||||
src/server.h | 2 +-
|
||||
src/t_hash.c | 13 +++-
|
||||
src/t_list.c | 30 ++++++++
|
||||
src/t_stream.c | 48 +++++++++---
|
||||
src/t_zset.c | 43 +++++++----
|
||||
src/ziplist.c | 17 ++++-
|
||||
src/ziplist.h | 1 +
|
||||
tests/support/util.tcl | 18 ++++-
|
||||
tests/unit/violations.tcl | 156 ++++++++++++++++++++++++++++++++++++++
|
||||
13 files changed, 339 insertions(+), 49 deletions(-)
|
||||
create mode 100644 tests/unit/violations.tcl
|
||||
|
||||
diff --git a/src/geo.c b/src/geo.c
|
||||
index 5c505441486c..a8710cd8b3ae 100644
|
||||
--- a/src/geo.c
|
||||
+++ b/src/geo.c
|
||||
@@ -635,7 +635,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
robj *zobj;
|
||||
zset *zs;
|
||||
int i;
|
||||
- size_t maxelelen = 0;
|
||||
+ size_t maxelelen = 0, totelelen = 0;
|
||||
|
||||
if (returned_items) {
|
||||
zobj = createZsetObject();
|
||||
@@ -650,13 +650,14 @@ void georadiusGeneric(client *c, int flags) {
|
||||
size_t elelen = sdslen(gp->member);
|
||||
|
||||
if (maxelelen < elelen) maxelelen = elelen;
|
||||
+ totelelen += elelen;
|
||||
znode = zslInsert(zs->zsl,score,gp->member);
|
||||
serverAssert(dictAdd(zs->dict,gp->member,&znode->score) == DICT_OK);
|
||||
gp->member = NULL;
|
||||
}
|
||||
|
||||
if (returned_items) {
|
||||
- zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
+ zsetConvertToZiplistIfNeeded(zobj,maxelelen,totelelen);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
diff --git a/src/listpack.c b/src/listpack.c
|
||||
index f8c34429ec1e..6c111e83e029 100644
|
||||
--- a/src/listpack.c
|
||||
+++ b/src/listpack.c
|
||||
@@ -283,7 +283,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
} else {
|
||||
if (size < 64) *enclen = 1+size;
|
||||
else if (size < 4096) *enclen = 2+size;
|
||||
- else *enclen = 5+size;
|
||||
+ else *enclen = 5+(uint64_t)size;
|
||||
return LP_ENCODING_STRING;
|
||||
}
|
||||
}
|
||||
diff --git a/src/quicklist.c b/src/quicklist.c
|
||||
index 52e3988f59b1..c4bf5274eb05 100644
|
||||
--- a/src/quicklist.c
|
||||
+++ b/src/quicklist.c
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
#include <string.h> /* for memcpy */
|
||||
+#include "redisassert.h"
|
||||
#include "quicklist.h"
|
||||
#include "zmalloc.h"
|
||||
#include "ziplist.h"
|
||||
@@ -43,11 +44,16 @@
|
||||
#define REDIS_STATIC static
|
||||
#endif
|
||||
|
||||
-/* Optimization levels for size-based filling */
|
||||
+/* Optimization levels for size-based filling.
|
||||
+ * Note that the largest possible limit is 16k, so even if each record takes
|
||||
+ * just one byte, it still won't overflow the 16 bit count field. */
|
||||
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
|
||||
/* Maximum size in bytes of any multi-element ziplist.
|
||||
- * Larger values will live in their own isolated ziplists. */
|
||||
+ * Larger values will live in their own isolated ziplists.
|
||||
+ * This is used only if we're limited by record count. when we're limited by
|
||||
+ * size, the maximum limit is bigger, but still safe.
|
||||
+ * 8k is a recommended / default size limit */
|
||||
#define SIZE_SAFETY_LIMIT 8192
|
||||
|
||||
/* Minimum ziplist size in bytes for attempting compression. */
|
||||
@@ -449,6 +455,8 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
|
||||
unsigned int new_sz = node->sz + sz + ziplist_overhead;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
|
||||
return 1;
|
||||
+ /* when we return 1 above we know that the limit is a size limit (which is
|
||||
+ * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(new_sz))
|
||||
return 0;
|
||||
else if ((int)node->count < fill)
|
||||
@@ -468,6 +476,8 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
unsigned int merge_sz = a->sz + b->sz - 11;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
|
||||
return 1;
|
||||
+ /* when we return 1 above we know that the limit is a size limit (which is
|
||||
+ * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(merge_sz))
|
||||
return 0;
|
||||
else if ((int)(a->count + b->count) <= fill)
|
||||
@@ -487,6 +497,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
* Returns 1 if new head created. */
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_head = quicklist->head;
|
||||
+ assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
|
||||
quicklist->head->zl =
|
||||
@@ -510,6 +521,7 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
* Returns 1 if new tail created. */
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_tail = quicklist->tail;
|
||||
+ assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
|
||||
quicklist->tail->zl =
|
||||
@@ -852,6 +864,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
|
||||
int fill = quicklist->fill;
|
||||
quicklistNode *node = entry->node;
|
||||
quicklistNode *new_node = NULL;
|
||||
+ assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
|
||||
if (!node) {
|
||||
/* we have no reference node, so let's create only node in the list */
|
||||
diff --git a/src/rdb.c b/src/rdb.c
|
||||
index ecd2c0e9870a..11cc41b56240 100644
|
||||
--- a/src/rdb.c
|
||||
+++ b/src/rdb.c
|
||||
@@ -1561,7 +1561,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_ZSET_2 || rdbtype == RDB_TYPE_ZSET) {
|
||||
/* Read list/set value. */
|
||||
uint64_t zsetlen;
|
||||
- size_t maxelelen = 0;
|
||||
+ size_t maxelelen = 0, totelelen = 0;
|
||||
zset *zs;
|
||||
|
||||
if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
@@ -1598,6 +1598,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
if (sdslen(sdsele) > maxelelen) maxelelen = sdslen(sdsele);
|
||||
+ totelelen += sdslen(sdsele);
|
||||
|
||||
znode = zslInsert(zs->zsl,score,sdsele);
|
||||
dictAdd(zs->dict,sdsele,&znode->score);
|
||||
@@ -1605,8 +1606,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Convert *after* loading, since sorted sets are not stored ordered. */
|
||||
if (zsetLength(o) <= server.zset_max_ziplist_entries &&
|
||||
- maxelelen <= server.zset_max_ziplist_value)
|
||||
- zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
+ maxelelen <= server.zset_max_ziplist_value &&
|
||||
+ ziplistSafeToAdd(NULL, totelelen))
|
||||
+ {
|
||||
+ zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
+ }
|
||||
} else if (rdbtype == RDB_TYPE_HASH) {
|
||||
uint64_t len;
|
||||
int ret;
|
||||
@@ -1635,21 +1639,25 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- /* Add pair to ziplist */
|
||||
- o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
||||
- sdslen(field), ZIPLIST_TAIL);
|
||||
- o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
||||
- sdslen(value), ZIPLIST_TAIL);
|
||||
-
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
- sdslen(value) > server.hash_max_ziplist_value)
|
||||
+ sdslen(value) > server.hash_max_ziplist_value ||
|
||||
+ !ziplistSafeToAdd(o->ptr, sdslen(field)+sdslen(value)))
|
||||
{
|
||||
- sdsfree(field);
|
||||
- sdsfree(value);
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
+ ret = dictAdd((dict*)o->ptr, field, value);
|
||||
+ if (ret == DICT_ERR) {
|
||||
+ rdbExitReportCorruptRDB("Duplicate hash fields detected");
|
||||
+ }
|
||||
break;
|
||||
}
|
||||
+
|
||||
+ /* Add pair to ziplist */
|
||||
+ o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
||||
+ sdslen(field), ZIPLIST_TAIL);
|
||||
+ o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
||||
+ sdslen(value), ZIPLIST_TAIL);
|
||||
+
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
}
|
||||
@@ -1726,6 +1734,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
||||
if (flen > maxlen) maxlen = flen;
|
||||
if (vlen > maxlen) maxlen = vlen;
|
||||
+ if (!ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
|
||||
+ rdbExitReportCorruptRDB("Hash zipmap too big (%u)", flen);
|
||||
+ }
|
||||
+
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
}
|
||||
diff --git a/src/server.h b/src/server.h
|
||||
index 530355421a8d..38774bbc2fa7 100644
|
||||
--- a/src/server.h
|
||||
+++ b/src/server.h
|
||||
@@ -1999,7 +1999,7 @@ unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned long zsetLength(const robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
-void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
|
||||
+void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen);
|
||||
int zsetScore(robj *zobj, sds member, double *score);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
|
||||
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore);
|
||||
diff --git a/src/t_hash.c b/src/t_hash.c
|
||||
index 8e79432a4fbd..3cdfdd169abf 100644
|
||||
--- a/src/t_hash.c
|
||||
+++ b/src/t_hash.c
|
||||
@@ -39,17 +39,22 @@
|
||||
* as their string length can be queried in constant time. */
|
||||
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
int i;
|
||||
+ size_t sum = 0;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
- if (sdsEncodedObject(argv[i]) &&
|
||||
- sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
||||
- {
|
||||
+ if (!sdsEncodedObject(argv[i]))
|
||||
+ continue;
|
||||
+ size_t len = sdslen(argv[i]->ptr);
|
||||
+ if (len > server.hash_max_ziplist_value) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
- break;
|
||||
+ return;
|
||||
}
|
||||
+ sum += len;
|
||||
}
|
||||
+ if (!ziplistSafeToAdd(o->ptr, sum))
|
||||
+ hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
}
|
||||
|
||||
/* Get the value from a ziplist encoded hash, identified by field.
|
||||
diff --git a/src/t_list.c b/src/t_list.c
|
||||
index 4f0bd7b81a5d..621b6932759e 100644
|
||||
--- a/src/t_list.c
|
||||
+++ b/src/t_list.c
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
+#define LIST_MAX_ITEM_SIZE ((1ull<<32)-1024)
|
||||
+
|
||||
/*-----------------------------------------------------------------------------
|
||||
* List API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -196,6 +198,14 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
|
||||
void pushGenericCommand(client *c, int where) {
|
||||
int j, pushed = 0;
|
||||
+
|
||||
+ for (j = 2; j < c->argc; j++) {
|
||||
+ if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
+ addReplyError(c, "Element too large");
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
|
||||
|
||||
if (lobj && lobj->type != OBJ_LIST) {
|
||||
@@ -277,6 +287,11 @@ void linsertCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
+ if (sdslen(c->argv[4]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
+ addReplyError(c, "Element too large");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,subject,OBJ_LIST)) return;
|
||||
|
||||
@@ -344,6 +359,11 @@ void lsetCommand(client *c) {
|
||||
long index;
|
||||
robj *value = c->argv[3];
|
||||
|
||||
+ if (sdslen(value->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
+ addReplyError(c, "Element too large");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -510,6 +530,11 @@ void lposCommand(client *c) {
|
||||
int direction = LIST_TAIL;
|
||||
long rank = 1, count = -1, maxlen = 0; /* Count -1: option not given. */
|
||||
|
||||
+ if (sdslen(ele->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
+ addReplyError(c, "Element too large");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
/* Parse the optional arguments. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
@@ -610,6 +635,11 @@ void lremCommand(client *c) {
|
||||
long toremove;
|
||||
long removed = 0;
|
||||
|
||||
+ if (sdslen(obj->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
+ addReplyError(c, "Element too large");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
diff --git a/src/t_stream.c b/src/t_stream.c
|
||||
index 43b67e8826ca..30411388697f 100644
|
||||
--- a/src/t_stream.c
|
||||
+++ b/src/t_stream.c
|
||||
@@ -40,6 +40,12 @@
|
||||
#define STREAM_ITEM_FLAG_DELETED (1<<0) /* Entry is deleted. Skip it. */
|
||||
#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1) /* Same fields as master entry. */
|
||||
|
||||
+/* Don't let listpacks grow too big, even if the user config allows it.
|
||||
+ * doing so can lead to an overflow (trying to store more than 32bit length
|
||||
+ * into the listpack header), or actually an assertion since lpInsert
|
||||
+ * will return NULL. */
|
||||
+#define STREAM_LISTPACK_MAX_SIZE (1<<30)
|
||||
+
|
||||
void streamFreeCG(streamCG *cg);
|
||||
void streamFreeNACK(streamNACK *na);
|
||||
size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer);
|
||||
@@ -191,8 +197,11 @@ int streamCompareID(streamID *a, streamID *b) {
|
||||
*
|
||||
* The function returns C_OK if the item was added, this is always true
|
||||
* if the ID was generated by the function. However the function may return
|
||||
- * C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
- * current top ID is greater or equal. */
|
||||
+ * C_ERR in several cases:
|
||||
+ * 1. If an ID was given via 'use_id', but adding it failed since the
|
||||
+ * current top ID is greater or equal. errno will be set to EDOM.
|
||||
+ * 2. If a size of a single element or the sum of the elements is too big to
|
||||
+ * be stored into the stream. errno will be set to ERANGE. */
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
@@ -206,7 +215,23 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* or return an error. Automatically generated IDs might
|
||||
* overflow (and wrap-around) when incrementing the sequence
|
||||
part. */
|
||||
- if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
|
||||
+ if (streamCompareID(&id,&s->last_id) <= 0) {
|
||||
+ errno = EDOM;
|
||||
+ return C_ERR;
|
||||
+ }
|
||||
+
|
||||
+ /* Avoid overflow when trying to add an element to the stream (listpack
|
||||
+ * can only host up to 32bit length sttrings, and also a total listpack size
|
||||
+ * can't be bigger than 32bit length. */
|
||||
+ size_t totelelen = 0;
|
||||
+ for (int64_t i = 0; i < numfields*2; i++) {
|
||||
+ sds ele = argv[i]->ptr;
|
||||
+ totelelen += sdslen(ele);
|
||||
+ }
|
||||
+ if (totelelen > STREAM_LISTPACK_MAX_SIZE) {
|
||||
+ errno = ERANGE;
|
||||
+ return C_ERR;
|
||||
+ }
|
||||
|
||||
/* Add the new entry. */
|
||||
raxIterator ri;
|
||||
@@ -265,9 +290,10 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* if we need to switch to the next one. 'lp' will be set to NULL if
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
- if (server.stream_node_max_bytes &&
|
||||
- lp_bytes >= server.stream_node_max_bytes)
|
||||
- {
|
||||
+ size_t node_max_bytes = server.stream_node_max_bytes;
|
||||
+ if (node_max_bytes == 0 || node_max_bytes > STREAM_LISTPACK_MAX_SIZE)
|
||||
+ node_max_bytes = STREAM_LISTPACK_MAX_SIZE;
|
||||
+ if (lp_bytes + totelelen >= node_max_bytes) {
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
int64_t count = lpGetInteger(lpFirst(lp));
|
||||
@@ -1267,11 +1293,13 @@ void xaddCommand(client *c) {
|
||||
|
||||
/* Append using the low level function and return the ID. */
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
- &id, id_given ? &id : NULL)
|
||||
- == C_ERR)
|
||||
+ &id, id_given ? &id : NULL) == C_ERR)
|
||||
{
|
||||
- addReplyError(c,"The ID specified in XADD is equal or smaller than the "
|
||||
- "target stream top item");
|
||||
+ if (errno == EDOM)
|
||||
+ addReplyError(c,"The ID specified in XADD is equal or smaller than "
|
||||
+ "the target stream top item");
|
||||
+ else
|
||||
+ addReplyError(c,"Elements are too large to be stored");
|
||||
return;
|
||||
}
|
||||
addReplyStreamID(c,&id);
|
||||
diff --git a/src/t_zset.c b/src/t_zset.c
|
||||
index d0ffe2f8b20a..b44f9551cc33 100644
|
||||
--- a/src/t_zset.c
|
||||
+++ b/src/t_zset.c
|
||||
@@ -1238,15 +1238,18 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
}
|
||||
|
||||
/* Convert the sorted set object into a ziplist if it is not already a ziplist
|
||||
- * and if the number of elements and the maximum element size is within the
|
||||
- * expected ranges. */
|
||||
-void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
|
||||
+ * and if the number of elements and the maximum element size and total elements size
|
||||
+ * are within the expected ranges. */
|
||||
+void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
|
||||
zset *zset = zobj->ptr;
|
||||
|
||||
if (zset->zsl->length <= server.zset_max_ziplist_entries &&
|
||||
- maxelelen <= server.zset_max_ziplist_value)
|
||||
- zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
+ maxelelen <= server.zset_max_ziplist_value &&
|
||||
+ ziplistSafeToAdd(NULL, totelelen))
|
||||
+ {
|
||||
+ zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Return (by reference) the score of the specified member of the sorted set
|
||||
@@ -1355,20 +1358,28 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
}
|
||||
return 1;
|
||||
} else if (!xx) {
|
||||
- /* Optimize: check if the element is too large or the list
|
||||
+ /* check if the element is too large or the list
|
||||
* becomes too long *before* executing zzlInsert. */
|
||||
- zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
- if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
|
||||
- sdslen(ele) > server.zset_max_ziplist_value)
|
||||
+ if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
|
||||
+ sdslen(ele) > server.zset_max_ziplist_value ||
|
||||
+ !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
|
||||
+ {
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
- if (newscore) *newscore = score;
|
||||
- *flags |= ZADD_ADDED;
|
||||
- return 1;
|
||||
+ } else {
|
||||
+ zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
+ if (newscore) *newscore = score;
|
||||
+ *flags |= ZADD_ADDED;
|
||||
+ return 1;
|
||||
+ }
|
||||
} else {
|
||||
*flags |= ZADD_NOP;
|
||||
return 1;
|
||||
}
|
||||
- } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
+ }
|
||||
+
|
||||
+ /* Note that the above block handling ziplist would have either returned or
|
||||
+ * converted the key to skiplist. */
|
||||
+ if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
zskiplistNode *znode;
|
||||
dictEntry *de;
|
||||
@@ -2180,7 +2191,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
zsetopsrc *src;
|
||||
zsetopval zval;
|
||||
sds tmp;
|
||||
- size_t maxelelen = 0;
|
||||
+ size_t maxelelen = 0, totelelen = 0;
|
||||
robj *dstobj;
|
||||
zset *dstzset;
|
||||
zskiplistNode *znode;
|
||||
@@ -2304,6 +2315,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
tmp = zuiNewSdsFromValue(&zval);
|
||||
znode = zslInsert(dstzset->zsl,score,tmp);
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
+ totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
}
|
||||
}
|
||||
@@ -2340,6 +2352,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
/* Remember the longest single element encountered,
|
||||
* to understand if it's possible to convert to ziplist
|
||||
* at the end. */
|
||||
+ totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
/* Update the element with its initial score. */
|
||||
dictSetKey(accumulator, de, tmp);
|
||||
@@ -2380,7 +2393,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
|
||||
if (dbDelete(c->db,dstkey))
|
||||
touched = 1;
|
||||
if (dstzset->zsl->length) {
|
||||
- zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
|
||||
+ zsetConvertToZiplistIfNeeded(dstobj,maxelelen,totelelen);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c,zsetLength(dstobj));
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
diff --git a/src/ziplist.c b/src/ziplist.c
|
||||
index 5933d19151b0..582eed2c9b4a 100644
|
||||
--- a/src/ziplist.c
|
||||
+++ b/src/ziplist.c
|
||||
@@ -265,6 +265,17 @@
|
||||
ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
|
||||
}
|
||||
|
||||
+/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
|
||||
+ * zlbytes*/
|
||||
+#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
|
||||
+int ziplistSafeToAdd(unsigned char* zl, size_t add) {
|
||||
+ size_t len = zl? ziplistBlobLen(zl): 0;
|
||||
+ if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
|
||||
+ return 0;
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+
|
||||
/* We use this function to receive information about a ziplist entry.
|
||||
* Note that this is not how the data is actually encoded, is just what we
|
||||
* get filled by a function in order to operate more easily. */
|
||||
@@ -586,7 +597,8 @@ unsigned char *ziplistNew(void) {
|
||||
}
|
||||
|
||||
/* Resize the ziplist. */
|
||||
-unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
+unsigned char *ziplistResize(unsigned char *zl, size_t len) {
|
||||
+ assert(len < UINT32_MAX);
|
||||
zl = zrealloc(zl,len);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(len);
|
||||
zl[len-1] = ZIP_END;
|
||||
@@ -898,6 +910,9 @@ unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
|
||||
/* Combined zl length should be limited within UINT16_MAX */
|
||||
zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;
|
||||
|
||||
+ /* larger values can't be stored into ZIPLIST_BYTES */
|
||||
+ assert(zlbytes < UINT32_MAX);
|
||||
+
|
||||
/* Save offset positions before we start ripping memory apart. */
|
||||
size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
|
||||
size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));
|
||||
diff --git a/src/ziplist.h b/src/ziplist.h
|
||||
index 964a47f6dc29..f6ba6c8be47d 100644
|
||||
--- a/src/ziplist.h
|
||||
+++ b/src/ziplist.h
|
||||
@@ -49,6 +49,7 @@ unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int v
|
||||
unsigned int ziplistLen(unsigned char *zl);
|
||||
size_t ziplistBlobLen(unsigned char *zl);
|
||||
void ziplistRepr(unsigned char *zl);
|
||||
+int ziplistSafeToAdd(unsigned char* zl, size_t add);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int ziplistTest(int argc, char *argv[]);
|
||||
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
|
||||
index 970d63314fd6..343912ac0049 100644
|
||||
--- a/tests/support/util.tcl
|
||||
+++ b/tests/support/util.tcl
|
||||
@@ -109,7 +109,23 @@ proc wait_done_loading r {
|
||||
|
||||
# count current log lines in server's stdout
|
||||
proc count_log_lines {srv_idx} {
|
||||
- set _ [exec wc -l < [srv $srv_idx stdout]]
|
||||
+ set _ [string trim [exec wc -l < [srv $srv_idx stdout]]]
|
||||
+}
|
||||
+
|
||||
+# returns the number of times a line with that pattern appears in a file
|
||||
+proc count_message_lines {file pattern} {
|
||||
+ set res 0
|
||||
+ # exec fails when grep exists with status other than 0 (when the patter wasn't found)
|
||||
+ catch {
|
||||
+ set res [string trim [exec grep $pattern $file 2> /dev/null | wc -l]]
|
||||
+ }
|
||||
+ return $res
|
||||
+}
|
||||
+
|
||||
+# returns the number of times a line with that pattern appears in the log
|
||||
+proc count_log_message {srv_idx pattern} {
|
||||
+ set stdout [srv $srv_idx stdout]
|
||||
+ return [count_message_lines $stdout $pattern]
|
||||
}
|
||||
|
||||
# verify pattern exists in server's sdtout after a certain line number
|
||||
diff --git a/tests/unit/violations.tcl b/tests/unit/violations.tcl
|
||||
new file mode 100644
|
||||
index 000000000000..d87b9236528e
|
||||
--- /dev/null
|
||||
+++ b/tests/unit/violations.tcl
|
||||
@@ -0,0 +1,156 @@
|
||||
+# These tests consume massive amounts of memory, and are not
|
||||
+# suitable to be executed as part of the normal test suite
|
||||
+set ::str500 [string repeat x 500000000] ;# 500mb
|
||||
+
|
||||
+# Utility function to write big argument into redis client connection
|
||||
+proc write_big_bulk {size} {
|
||||
+ r write "\$$size\r\n"
|
||||
+ while {$size >= 500000000} {
|
||||
+ r write $::str500
|
||||
+ incr size -500000000
|
||||
+ }
|
||||
+ if {$size > 0} {
|
||||
+ r write [string repeat x $size]
|
||||
+ }
|
||||
+ r write "\r\n"
|
||||
+}
|
||||
+
|
||||
+# One XADD with one huge 5GB field
|
||||
+# Expected to fail resulting in an empty stream
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {XADD one huge field} {
|
||||
+ r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
+ r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
+ r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n"
|
||||
+ r write "\$1\r\nA\r\n"
|
||||
+ write_big_bulk 5000000000 ;#5gb
|
||||
+ r flush
|
||||
+ catch {r read} err
|
||||
+ assert_match {*too large*} $err
|
||||
+ r xlen S1
|
||||
+ } {0}
|
||||
+}
|
||||
+
|
||||
+# One XADD with one huge (exactly nearly) 4GB field
|
||||
+# This uncovers the overflow in lpEncodeGetType
|
||||
+# Expected to fail resulting in an empty stream
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {XADD one huge field - 1} {
|
||||
+ r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
+ r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
+ r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n"
|
||||
+ r write "\$1\r\nA\r\n"
|
||||
+ write_big_bulk 4294967295 ;#4gb-1
|
||||
+ r flush
|
||||
+ catch {r read} err
|
||||
+ assert_match {*too large*} $err
|
||||
+ r xlen S1
|
||||
+ } {0}
|
||||
+}
|
||||
+
|
||||
+# Gradually add big stream fields using repeated XADD calls
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {several XADD big fields} {
|
||||
+ r config set stream-node-max-bytes 0
|
||||
+ for {set j 0} {$j<10} {incr j} {
|
||||
+ r xadd stream * 1 $::str500 2 $::str500
|
||||
+ }
|
||||
+ r ping
|
||||
+ r xlen stream
|
||||
+ } {10}
|
||||
+}
|
||||
+
|
||||
+# Add over 4GB to a single stream listpack (one XADD command)
|
||||
+# Expected to fail resulting in an empty stream
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {single XADD big fields} {
|
||||
+ r write "*23\r\n\$4\r\nXADD\r\n\$1\r\nS\r\n\$1\r\n*\r\n"
|
||||
+ for {set j 0} {$j<10} {incr j} {
|
||||
+ r write "\$1\r\n$j\r\n"
|
||||
+ write_big_bulk 500000000 ;#500mb
|
||||
+ }
|
||||
+ r flush
|
||||
+ catch {r read} err
|
||||
+ assert_match {*too large*} $err
|
||||
+ r xlen S
|
||||
+ } {0}
|
||||
+}
|
||||
+
|
||||
+# Gradually add big hash fields using repeated HSET calls
|
||||
+# This reproduces the overflow in the call to ziplistResize
|
||||
+# Object will be converted to hashtable encoding
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ r config set hash-max-ziplist-value 1000000000 ;#1gb
|
||||
+ test {hash with many big fields} {
|
||||
+ for {set j 0} {$j<10} {incr j} {
|
||||
+ r hset h $j $::str500
|
||||
+ }
|
||||
+ r object encoding h
|
||||
+ } {hashtable}
|
||||
+}
|
||||
+
|
||||
+# Add over 4GB to a single hash field (one HSET command)
|
||||
+# Object will be converted to hashtable encoding
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {hash with one huge field} {
|
||||
+ catch {r config set hash-max-ziplist-value 10000000000} ;#10gb
|
||||
+ r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
+ r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
+ r write "*4\r\n\$4\r\nHSET\r\n\$2\r\nH1\r\n"
|
||||
+ r write "\$1\r\nA\r\n"
|
||||
+ write_big_bulk 5000000000 ;#5gb
|
||||
+ r flush
|
||||
+ r read
|
||||
+ r object encoding H1
|
||||
+ } {hashtable}
|
||||
+}
|
||||
+
|
||||
+# Add over 4GB to a single list member (one LPUSH command)
|
||||
+# Currently unsupported, and expected to fail rather than being truncated
|
||||
+# Expected to fail resulting in a non-existing list
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {list with one huge field} {
|
||||
+ r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
+ r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
+ r write "*3\r\n\$5\r\nLPUSH\r\n\$2\r\nL1\r\n"
|
||||
+ write_big_bulk 5000000000 ;#5gb
|
||||
+ r flush
|
||||
+ catch {r read} err
|
||||
+ assert_match {*too large*} $err
|
||||
+ r exists L1
|
||||
+ } {0}
|
||||
+}
|
||||
+
|
||||
+# SORT which attempts to store an element larger than 4GB into a list.
|
||||
+# Currently unsupported and results in an assertion instead of truncation
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {SORT adds huge field to list} {
|
||||
+ r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
+ r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
+ r write "*3\r\n\$3\r\nSET\r\n\$2\r\nS1\r\n"
|
||||
+ write_big_bulk 5000000000 ;#5gb
|
||||
+ r flush
|
||||
+ r read
|
||||
+ assert_equal [r strlen S1] 5000000000
|
||||
+ r set S2 asdf
|
||||
+ r sadd myset 1 2
|
||||
+ r mset D1 1 D2 2
|
||||
+ catch {r sort myset by D* get S* store mylist}
|
||||
+ # assert_equal [count_log_message 0 "crashed by signal"] 0 - not suitable for 6.0
|
||||
+ assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+# SORT which stores an integer encoded element into a list.
|
||||
+# Just for coverage, no news here.
|
||||
+start_server [list overrides [list save ""] ] {
|
||||
+ test {SORT adds integer field to list} {
|
||||
+ r set S1 asdf
|
||||
+ r set S2 123 ;# integer encoded
|
||||
+ assert_encoding "int" S2
|
||||
+ r sadd myset 1 2
|
||||
+ r mset D1 1 D2 2
|
||||
+ r sort myset by D* get S* store mylist
|
||||
+ r llen mylist
|
||||
+ } {2}
|
||||
+}
|
117
SOURCES/redis-CVE-2021-32675.patch
Normal file
117
SOURCES/redis-CVE-2021-32675.patch
Normal file
@ -0,0 +1,117 @@
|
||||
From 5674b0057ff2903d43eaff802017eddf37c360f8 Mon Sep 17 00:00:00 2001
|
||||
From: Oran Agra <oran@redislabs.com>
|
||||
Date: Wed, 9 Jun 2021 17:31:39 +0300
|
||||
Subject: [PATCH] Prevent unauthenticated client from easily consuming lots of
|
||||
memory (CVE-2021-32675)
|
||||
|
||||
This change sets a low limit for multibulk and bulk length in the
|
||||
protocol for unauthenticated connections, so that they can't easily
|
||||
cause redis to allocate massive amounts of memory by sending just a few
|
||||
characters on the network.
|
||||
The new limits are 10 arguments of 16kb each (instead of 1m of 512mb)
|
||||
---
|
||||
src/networking.c | 17 +++++++++++++++++
|
||||
src/server.c | 9 ++-------
|
||||
src/server.h | 1 +
|
||||
tests/unit/auth.tcl | 16 ++++++++++++++++
|
||||
4 files changed, 36 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/networking.c b/src/networking.c
|
||||
index a61678dab2b..b02397c96f4 100644
|
||||
--- a/src/networking.c
|
||||
+++ b/src/networking.c
|
||||
@@ -97,6 +97,15 @@ void linkClient(client *c) {
|
||||
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
|
||||
}
|
||||
|
||||
+int authRequired(client *c) {
|
||||
+ /* Check if the user is authenticated. This check is skipped in case
|
||||
+ * the default user is flagged as "nopass" and is active. */
|
||||
+ int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
+ (DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
+ !c->authenticated;
|
||||
+ return auth_required;
|
||||
+}
|
||||
+
|
||||
client *createClient(connection *conn) {
|
||||
client *c = zmalloc(sizeof(client));
|
||||
|
||||
@@ -1744,6 +1753,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError("invalid mbulk count",c);
|
||||
return C_ERR;
|
||||
+ } else if (ll > 10 && authRequired(c)) {
|
||||
+ addReplyError(c, "Protocol error: unauthenticated multibulk length");
|
||||
+ setProtocolError("unauth mbulk count", c);
|
||||
+ return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = (newline-c->querybuf)+2;
|
||||
@@ -1791,6 +1804,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c);
|
||||
return C_ERR;
|
||||
+ } else if (ll > 16384 && authRequired(c)) {
|
||||
+ addReplyError(c, "Protocol error: unauthenticated bulk length");
|
||||
+ setProtocolError("unauth bulk length", c);
|
||||
+ return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = newline-c->querybuf+2;
|
||||
diff --git a/src/server.c b/src/server.c
|
||||
index 93148f8e3ed..c8768b1824b 100644
|
||||
--- a/src/server.c
|
||||
+++ b/src/server.c
|
||||
@@ -3590,13 +3590,8 @@ int processCommand(client *c) {
|
||||
int is_denyloading_command = !(c->cmd->flags & CMD_LOADING) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_LOADING));
|
||||
|
||||
- /* Check if the user is authenticated. This check is skipped in case
|
||||
- * the default user is flagged as "nopass" and is active. */
|
||||
- int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
- (DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
- !c->authenticated;
|
||||
- if (auth_required) {
|
||||
- /* AUTH and HELLO and no auth modules are valid even in
|
||||
+ if (authRequired(c)) {
|
||||
+ /* AUTH and HELLO and no auth commands are valid even in
|
||||
* non-authenticated state. */
|
||||
if (!(c->cmd->flags & CMD_NO_AUTH)) {
|
||||
rejectCommand(c,shared.noautherr);
|
||||
diff --git a/src/server.h b/src/server.h
|
||||
index a16f2885829..530355421a8 100644
|
||||
--- a/src/server.h
|
||||
+++ b/src/server.h
|
||||
@@ -1743,6 +1743,7 @@ void protectClient(client *c);
|
||||
void unprotectClient(client *c);
|
||||
void initThreadedIO(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
+int authRequired(client *c);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
diff --git a/tests/unit/auth.tcl b/tests/unit/auth.tcl
|
||||
index 9080d4bf7e9..530ca7c8d91 100644
|
||||
--- a/tests/unit/auth.tcl
|
||||
+++ b/tests/unit/auth.tcl
|
||||
@@ -24,4 +24,20 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
|
||||
r set foo 100
|
||||
r incr foo
|
||||
} {101}
|
||||
+
|
||||
+ test {For unauthenticated clients multibulk and bulk length are limited} {
|
||||
+ set rr [redis [srv "host"] [srv "port"] 0 $::tls]
|
||||
+ $rr write "*100\r\n"
|
||||
+ $rr flush
|
||||
+ catch {[$rr read]} e
|
||||
+ assert_match {*unauthenticated multibulk length*} $e
|
||||
+ $rr close
|
||||
+
|
||||
+ set rr [redis [srv "host"] [srv "port"] 0 $::tls]
|
||||
+ $rr write "*1\r\n\$100000000\r\n"
|
||||
+ $rr flush
|
||||
+ catch {[$rr read]} e
|
||||
+ assert_match {*unauthenticated bulk length*} $e
|
||||
+ $rr close
|
||||
+ }
|
||||
}
|
67
SOURCES/redis-CVE-2021-32687.patch
Normal file
67
SOURCES/redis-CVE-2021-32687.patch
Normal file
@ -0,0 +1,67 @@
|
||||
From a30d367a71b7017581cf1ca104242a3c644dec0f Mon Sep 17 00:00:00 2001
|
||||
From: Oran Agra <oran@redislabs.com>
|
||||
Date: Sun, 26 Sep 2021 15:42:17 +0300
|
||||
Subject: [PATCH] Fix Integer overflow issue with intsets (CVE-2021-32687)
|
||||
|
||||
The vulnerability involves changing the default set-max-intset-entries
|
||||
configuration parameter to a very large value and constructing specially
|
||||
crafted commands to manipulate sets
|
||||
---
|
||||
src/intset.c | 4 +++-
|
||||
src/rdb.c | 4 +++-
|
||||
src/t_set.c | 5 ++++-
|
||||
3 files changed, 10 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/intset.c b/src/intset.c
|
||||
index 19f6562ed69c..3f8b754e3a3e 100644
|
||||
--- a/src/intset.c
|
||||
+++ b/src/intset.c
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "intset.h"
|
||||
#include "zmalloc.h"
|
||||
#include "endianconv.h"
|
||||
+#include "redisassert.h"
|
||||
|
||||
/* Note that these encodings are ordered, so:
|
||||
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
|
||||
@@ -103,7 +104,8 @@ intset *intsetNew(void) {
|
||||
|
||||
/* Resize the intset */
|
||||
static intset *intsetResize(intset *is, uint32_t len) {
|
||||
- uint32_t size = len*intrev32ifbe(is->encoding);
|
||||
+ uint64_t size = (uint64_t)len*intrev32ifbe(is->encoding);
|
||||
+ assert(size <= SIZE_MAX - sizeof(intset));
|
||||
is = zrealloc(is,sizeof(intset)+size);
|
||||
return is;
|
||||
}
|
||||
diff --git a/src/rdb.c b/src/rdb.c
|
||||
index 82b4cf5065d1..ecd2c0e9870a 100644
|
||||
--- a/src/rdb.c
|
||||
+++ b/src/rdb.c
|
||||
@@ -1518,7 +1518,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
|
||||
/* Use a regular set when there are too many entries. */
|
||||
- if (len > server.set_max_intset_entries) {
|
||||
+ size_t max_entries = server.set_max_intset_entries;
|
||||
+ if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
+ if (len > max_entries) {
|
||||
o = createSetObject();
|
||||
/* It's faster to expand the dict to the right size asap in order
|
||||
* to avoid rehashing */
|
||||
diff --git a/src/t_set.c b/src/t_set.c
|
||||
index f32565e41102..eea45c8df2d8 100644
|
||||
--- a/src/t_set.c
|
||||
+++ b/src/t_set.c
|
||||
@@ -66,7 +66,10 @@ int setTypeAdd(robj *subject, sds value) {
|
||||
if (success) {
|
||||
/* Convert to regular set when the intset contains
|
||||
* too many entries. */
|
||||
- if (intsetLen(subject->ptr) > server.set_max_intset_entries)
|
||||
+ size_t max_entries = server.set_max_intset_entries;
|
||||
+ /* limit to 1G entries due to intset internals. */
|
||||
+ if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
+ if (intsetLen(subject->ptr) > max_entries)
|
||||
setTypeConvert(subject,OBJ_ENCODING_HT);
|
||||
return 1;
|
||||
}
|
91
SOURCES/redis-CVE-2021-41099.patch
Normal file
91
SOURCES/redis-CVE-2021-41099.patch
Normal file
@ -0,0 +1,91 @@
|
||||
Backported for 6.0.9
|
||||
|
||||
|
||||
From c992857618db99776917f10bf4f2345a5fdc78b0 Mon Sep 17 00:00:00 2001
|
||||
From: Yossi Gottlieb <yossigo@gmail.com>
|
||||
Date: Mon, 22 Feb 2021 15:41:32 +0200
|
||||
Subject: [PATCH] Fix integer overflow (CVE-2021-21309). (#8522)
|
||||
|
||||
On 32-bit systems, setting the proto-max-bulk-len config parameter to a high value may result with integer overflow and a subsequent heap overflow when parsing an input bulk (CVE-2021-21309).
|
||||
|
||||
This fix has two parts:
|
||||
|
||||
Set a reasonable limit to the config parameter.
|
||||
Add additional checks to prevent the problem in other potential but unknown code paths.
|
||||
|
||||
(cherry picked from commit d32f2e9999ce003bad0bd2c3bca29f64dcce4433)
|
||||
---
|
||||
src/config.c | 2 +-
|
||||
src/sds.c | 3 +++
|
||||
src/zmalloc.c | 10 ++++++++++
|
||||
3 files changed, 14 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/sds.c b/src/sds.c
|
||||
index dc664ca9bc4f..4dbb41d2b703 100644
|
||||
--- a/src/sds.c
|
||||
+++ b/src/sds.c
|
||||
@@ -96,6 +96,7 @@ sds sdsnewlen(const void *init, size_t initlen) {
|
||||
int hdrlen = sdsHdrSize(type);
|
||||
unsigned char *fp; /* flags pointer. */
|
||||
|
||||
+ assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
|
||||
sh = s_malloc(hdrlen+initlen+1);
|
||||
if (sh == NULL) return NULL;
|
||||
if (init==SDS_NOINIT)
|
||||
@@ -214,6 +215,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
len = sdslen(s);
|
||||
sh = (char*)s-sdsHdrSize(oldtype);
|
||||
newlen = (len+addlen);
|
||||
+ assert(newlen > len); /* Catch size_t overflow */
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
else
|
||||
@@ -227,6 +229,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
|
||||
hdrlen = sdsHdrSize(type);
|
||||
+ assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
|
||||
From c6ad876774f3cc11e32681ea02a2eead00f2c521 Mon Sep 17 00:00:00 2001
|
||||
From: YiyuanGUO <yguoaz@gmail.com>
|
||||
Date: Wed, 29 Sep 2021 10:20:35 +0300
|
||||
Subject: [PATCH] Fix integer overflow in _sdsMakeRoomFor (CVE-2021-41099)
|
||||
|
||||
---
|
||||
src/sds.c | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/sds.c b/src/sds.c
|
||||
index 4dbb41d2b703..a43a1a5cbeed 100644
|
||||
--- a/src/sds.c
|
||||
+++ b/src/sds.c
|
||||
@@ -205,7 +205,7 @@ void sdsclear(sds s) {
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
void *sh, *newsh;
|
||||
size_t avail = sdsavail(s);
|
||||
- size_t len, newlen;
|
||||
+ size_t len, newlen, reqlen;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen;
|
||||
|
||||
@@ -214,7 +214,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
|
||||
len = sdslen(s);
|
||||
sh = (char*)s-sdsHdrSize(oldtype);
|
||||
- newlen = (len+addlen);
|
||||
+ reqlen = newlen = (len+addlen);
|
||||
assert(newlen > len); /* Catch size_t overflow */
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
@@ -229,7 +229,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
|
||||
hdrlen = sdsHdrSize(type);
|
||||
- assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
|
||||
+ assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) return NULL;
|
@ -20,7 +20,7 @@
|
||||
|
||||
Name: redis
|
||||
Version: 6.0.9
|
||||
Release: 3%{?dist}
|
||||
Release: 5%{?dist}
|
||||
Summary: A persistent key-value database
|
||||
# redis, jemalloc, linenoise, lzf, hiredis are BSD
|
||||
# lua is MIT
|
||||
@ -53,6 +53,11 @@ Patch0003: redis-config.patch
|
||||
|
||||
# Security patches
|
||||
Patch100: redis-CVE-2021-26477.patch
|
||||
Patch101: redis-CVE-2021-32687.patch
|
||||
Patch102: redis-CVE-2021-32626.patch
|
||||
Patch103: redis-CVE-2021-32627.patch
|
||||
Patch104: redis-CVE-2021-41099.patch
|
||||
Patch105: redis-CVE-2021-32675.patch
|
||||
|
||||
BuildRequires: gcc
|
||||
%if %{with tests}
|
||||
@ -137,7 +142,12 @@ mv ../%{name}-doc-%{doc_commit} doc
|
||||
%patch0001 -p1
|
||||
%patch0002 -p1
|
||||
%patch0003 -p1
|
||||
%patch100 -p1 -b .cve29477
|
||||
%patch100 -p1 -b .cve-2011-29477
|
||||
%patch101 -p1 -b .cve-2011-32687
|
||||
%patch102 -p1 -b .cve-2011-32626
|
||||
%patch103 -p1 -b .cve-2011-32627
|
||||
%patch104 -p1 -b .cve-2011-41099
|
||||
%patch105 -p1 -b .cve-2011-32675
|
||||
|
||||
mv deps/lua/COPYRIGHT COPYRIGHT-lua
|
||||
mv deps/jemalloc/COPYING COPYING-jemalloc
|
||||
@ -286,6 +296,22 @@ exit 0
|
||||
|
||||
|
||||
%changelog
|
||||
* Mon Oct 11 2021 Remi Collet <rcollet@redhat.com> - 6.0.9-5
|
||||
- fix denial of service via Redis Standard Protocol (RESP) request
|
||||
CVE-2021-32675
|
||||
|
||||
* Fri Oct 8 2021 Remi Collet <rcollet@redhat.com> - 6.0.9-4
|
||||
- fix lua scripts can overflow the heap-based Lua stack
|
||||
CVE-2021-32626
|
||||
- fix integer overflow issue with Streams
|
||||
CVE-2021-32627
|
||||
- fix integer overflow bug in the ziplist data structure
|
||||
CVE-2021-32628
|
||||
- fix integer overflow issue with intsets
|
||||
CVE-2021-32687
|
||||
- fix integer overflow issue with strings
|
||||
CVE-2021-41099
|
||||
|
||||
* Wed May 12 2021 Remi Collet <rcollet@redhat.com> - 6.0.9-3
|
||||
- fix integer overflow via STRALGO LCS command
|
||||
CVE-2021-29477
|
||||
|
Loading…
Reference in New Issue
Block a user