aboutsummaryrefslogtreecommitdiffstats
path: root/meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch')
-rw-r--r--meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch873
1 files changed, 0 insertions, 873 deletions
diff --git a/meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch b/meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch
deleted file mode 100644
index 3c60a3e678..0000000000
--- a/meta-oe/recipes-extended/redis/redis/CVE-2021-32627-CVE-2021-32628.patch
+++ /dev/null
@@ -1,873 +0,0 @@
-From 2775a3526e3e8bb040e72995231632c801977395 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.
-
-CVE: CVE-2021-32627,CVE-2021-32628
-Upstream-Status: Backport[https://github.com/redis/redis/commit/f6a40570fa63d5afdd596c78083d754081d80ae3]
-
-Signed-off-by: Changqing Li <changqing.li@windriver.com>
-
----
- src/geo.c | 5 +-
- src/listpack.c | 2 +-
- src/module.c | 6 +-
- src/quicklist.c | 16 +++-
- src/rdb.c | 45 +++++++----
- src/server.h | 2 +-
- src/t_hash.c | 13 +++-
- src/t_list.c | 29 +++++++
- src/t_stream.c | 48 +++++++++---
- src/t_zset.c | 62 +++++++++------
- src/ziplist.c | 17 ++++-
- src/ziplist.h | 1 +
- tests/unit/violations.tcl | 156 ++++++++++++++++++++++++++++++++++++++
- 13 files changed, 341 insertions(+), 61 deletions(-)
- create mode 100644 tests/unit/violations.tcl
-
-diff --git a/src/geo.c b/src/geo.c
-index 7c75738a2..893f78a7e 100644
---- a/src/geo.c
-+++ b/src/geo.c
-@@ -770,7 +770,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
- robj *zobj;
- zset *zs;
- int i;
-- size_t maxelelen = 0;
-+ size_t maxelelen = 0, totelelen = 0;
-
- if (returned_items) {
- zobj = createZsetObject();
-@@ -785,13 +785,14 @@ void georadiusGeneric(client *c, int srcKeyIndex, 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,flags & GEOSEARCH ? "geosearchstore" : "georadiusstore",storekey,
-diff --git a/src/listpack.c b/src/listpack.c
-index ee256bad3..27622d4a5 100644
---- a/src/listpack.c
-+++ b/src/listpack.c
-@@ -313,7 +313,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/module.c b/src/module.c
-index bf6580a60..adca9dc9c 100644
---- a/src/module.c
-+++ b/src/module.c
-@@ -3319,6 +3319,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
- * - EDOM if the given ID was 0-0 or not greater than all other IDs in the
- * stream (only if the AUTOID flag is unset)
- * - EFBIG if the stream has reached the last possible ID
-+ * - ERANGE if the elements are too large to be stored.
- */
- int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisModuleString **argv, long numfields) {
- /* Validate args */
-@@ -3362,8 +3363,9 @@ int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisM
- use_id_ptr = &use_id;
- }
- if (streamAppendItem(s, argv, numfields, &added_id, use_id_ptr) == C_ERR) {
-- /* ID not greater than all existing IDs in the stream */
-- errno = EDOM;
-+ /* Either the ID not greater than all existing IDs in the stream, or
-+ * the elements are too large to be stored. either way, errno is already
-+ * set by streamAppendItem. */
- return REDISMODULE_ERR;
- }
- /* Postponed signalKeyAsReady(). Done implicitly by moduleCreateEmptyKey()
-diff --git a/src/quicklist.c b/src/quicklist.c
-index 5a1e41dcc..a9f8b43b1 100644
---- a/src/quicklist.c
-+++ b/src/quicklist.c
-@@ -45,11 +45,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. */
-@@ -444,6 +449,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)
-@@ -463,6 +470,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)
-@@ -482,6 +491,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 =
-@@ -505,6 +515,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 =
-@@ -847,6 +858,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 53f67a72e..5456c1d80 100644
---- a/src/rdb.c
-+++ b/src/rdb.c
-@@ -1625,7 +1625,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;
-@@ -1665,6 +1665,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);
- if (dictAdd(zs->dict,sdsele,&znode->score) != DICT_OK) {
-@@ -1677,8 +1678,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;
-@@ -1731,21 +1735,30 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
- }
- }
-
-- /* 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) {
-+ rdbReportCorruptRDB("Duplicate hash fields detected");
-+ if (dupSearchDict) dictRelease(dupSearchDict);
-+ sdsfree(value);
-+ sdsfree(field);
-+ decrRefCount(o);
-+ return NULL;
-+ }
- 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);
- }
-@@ -1858,12 +1871,11 @@ 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;
-- zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
-- zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
-
- /* search for duplicate records */
- sds field = sdstrynewlen(fstr, flen);
-- if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK) {
-+ if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK ||
-+ !ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
- rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen);
- dictRelease(dupSearchDict);
- sdsfree(field);
-@@ -1872,6 +1884,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
- decrRefCount(o);
- return NULL;
- }
-+
-+ zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
-+ zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
- }
-
- dictRelease(dupSearchDict);
-diff --git a/src/server.h b/src/server.h
-index d9fef9552..07b34c743 100644
---- a/src/server.h
-+++ b/src/server.h
-@@ -2173,7 +2173,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 in_flags, int *out_flags, double *newscore);
-diff --git a/src/t_hash.c b/src/t_hash.c
-index ea0606fb0..2720fdbc7 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 f8ca27458..66c9e3c9d 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
- *----------------------------------------------------------------------------*/
-@@ -224,6 +226,13 @@ robj *listTypeDup(robj *o) {
- void pushGenericCommand(client *c, int where, int xx) {
- int j;
-
-+ 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 (checkType(c,lobj,OBJ_LIST)) return;
- if (!lobj) {
-@@ -287,6 +296,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;
-
-@@ -354,6 +368,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;
-
-@@ -576,6 +595,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;
-@@ -671,6 +695,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 2c30faa06..574195ee3 100644
---- a/src/t_stream.c
-+++ b/src/t_stream.c
-@@ -47,6 +47,12 @@
- * setting stream_node_max_bytes to a huge number. */
- #define STREAM_LISTPACK_MAX_PRE_ALLOCATE 4096
-
-+/* 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);
-@@ -433,8 +439,11 @@ void streamGetEdgeID(stream *s, int first, streamID *edge_id)
- *
- * 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. */
-@@ -448,7 +457,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;
-@@ -507,9 +532,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) {
- unsigned char *lp_ele = lpFirst(lp);
-@@ -1796,11 +1822,13 @@ void xaddCommand(client *c) {
- /* Append using the low level function and return the ID. */
- streamID id;
- if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
-- &id, parsed_args.id_given ? &parsed_args.id : NULL)
-- == C_ERR)
-+ &id, parsed_args.id_given ? &parsed_args.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 3b9ebd2bd..2abc1b49b 100644
---- a/src/t_zset.c
-+++ b/src/t_zset.c
-@@ -1242,15 +1242,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
-@@ -1370,20 +1373,28 @@ int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, dou
- }
- 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;
-- *out_flags |= ZADD_OUT_ADDED;
-- return 1;
-+ } else {
-+ zobj->ptr = zzlInsert(zobj->ptr,ele,score);
-+ if (newscore) *newscore = score;
-+ *out_flags |= ZADD_OUT_ADDED;
-+ return 1;
-+ }
- } else {
- *out_flags |= ZADD_OUT_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;
-@@ -2361,7 +2372,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
- }
- }
-
--static int zsetDictGetMaxElementLength(dict *d) {
-+static size_t zsetDictGetMaxElementLength(dict *d, size_t *totallen) {
- dictIterator *di;
- dictEntry *de;
- size_t maxelelen = 0;
-@@ -2371,6 +2382,8 @@ static int zsetDictGetMaxElementLength(dict *d) {
- while((de = dictNext(di)) != NULL) {
- sds ele = dictGetKey(de);
- if (sdslen(ele) > maxelelen) maxelelen = sdslen(ele);
-+ if (totallen)
-+ (*totallen) += sdslen(ele);
- }
-
- dictReleaseIterator(di);
-@@ -2378,7 +2391,7 @@ static int zsetDictGetMaxElementLength(dict *d) {
- return maxelelen;
- }
-
--static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
-+static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
- /* DIFF Algorithm 1:
- *
- * We perform the diff by iterating all the elements of the first set,
-@@ -2426,13 +2439,14 @@ static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *
- znode = zslInsert(dstzset->zsl,zval.score,tmp);
- dictAdd(dstzset->dict,tmp,&znode->score);
- if (sdslen(tmp) > *maxelelen) *maxelelen = sdslen(tmp);
-+ (*totelelen) += sdslen(tmp);
- }
- }
- zuiClearIterator(&src[0]);
- }
-
-
--static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
-+static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
- /* DIFF Algorithm 2:
- *
- * Add all the elements of the first set to the auxiliary set.
-@@ -2486,7 +2500,7 @@ static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *
-
- /* Using this algorithm, we can't calculate the max element as we go,
- * we have to iterate through all elements to find the max one after. */
-- *maxelelen = zsetDictGetMaxElementLength(dstzset->dict);
-+ *maxelelen = zsetDictGetMaxElementLength(dstzset->dict, totelelen);
- }
-
- static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
-@@ -2523,14 +2537,14 @@ static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
- return (algo_one_work <= algo_two_work) ? 1 : 2;
- }
-
--static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
-+static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
- /* Skip everything if the smallest input is empty. */
- if (zuiLength(&src[0]) > 0) {
- int diff_algo = zsetChooseDiffAlgorithm(src, setnum);
- if (diff_algo == 1) {
-- zdiffAlgorithm1(src, setnum, dstzset, maxelelen);
-+ zdiffAlgorithm1(src, setnum, dstzset, maxelelen, totelelen);
- } else if (diff_algo == 2) {
-- zdiffAlgorithm2(src, setnum, dstzset, maxelelen);
-+ zdiffAlgorithm2(src, setnum, dstzset, maxelelen, totelelen);
- } else if (diff_algo != 0) {
- serverPanic("Unknown algorithm");
- }
-@@ -2565,7 +2579,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
- zsetopsrc *src;
- zsetopval zval;
- sds tmp;
-- size_t maxelelen = 0;
-+ size_t maxelelen = 0, totelelen = 0;
- robj *dstobj;
- zset *dstzset;
- zskiplistNode *znode;
-@@ -2701,6 +2715,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
- 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);
- }
- }
-@@ -2737,6 +2752,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
- /* 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);
-@@ -2771,14 +2787,14 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
- dictReleaseIterator(di);
- dictRelease(accumulator);
- } else if (op == SET_OP_DIFF) {
-- zdiff(src, setnum, dstzset, &maxelelen);
-+ zdiff(src, setnum, dstzset, &maxelelen, &totelelen);
- } else {
- serverPanic("Unknown operator");
- }
-
- if (dstkey) {
- if (dstzset->zsl->length) {
-- zsetConvertToZiplistIfNeeded(dstobj, maxelelen);
-+ zsetConvertToZiplistIfNeeded(dstobj, maxelelen, totelelen);
- setKey(c, c->db, dstkey, dstobj);
- addReplyLongLong(c, zsetLength(dstobj));
- notifyKeyspaceEvent(NOTIFY_ZSET,
-diff --git a/src/ziplist.c b/src/ziplist.c
-index aae86c1f2..fdc1bb9e1 100644
---- a/src/ziplist.c
-+++ b/src/ziplist.c
-@@ -267,6 +267,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. */
-@@ -709,7 +720,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;
-@@ -1070,6 +1082,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 9e7997ad8..569e1259d 100644
---- a/src/ziplist.h
-+++ b/src/ziplist.h
-@@ -65,6 +65,7 @@ int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
- void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val);
- void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
- unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
-+int ziplistSafeToAdd(unsigned char* zl, size_t add);
-
- #ifdef REDIS_TEST
- int ziplistTest(int argc, char *argv[], int accurate);
-diff --git a/tests/unit/violations.tcl b/tests/unit/violations.tcl
-new file mode 100644
-index 000000000..1d3140c52
---- /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
-+ 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}
-+}
---
-2.17.1
-