Blame SOURCES/redis-CVE-2021-32627.patch

3a8238
Backported for 5.0.5
3a8238
3a8238
3a8238
3a8238
From 6facfb7a103b26b9a602253a738b2130afb7c5d3 Mon Sep 17 00:00:00 2001
3a8238
From: Oran Agra <oran@redislabs.com>
3a8238
Date: Thu, 3 Jun 2021 12:10:02 +0300
3a8238
Subject: [PATCH] Fix ziplist and listpack overflows and truncations
3a8238
 (CVE-2021-32627, CVE-2021-32628)
3a8238
3a8238
- fix possible heap corruption in ziplist and listpack resulting by trying to
3a8238
  allocate more than the maximum size of 4GB.
3a8238
- prevent ziplist (hash and zset) from reaching size of above 1GB, will be
3a8238
  converted to HT encoding, that's not a useful size.
3a8238
- prevent listpack (stream) from reaching size of above 1GB.
3a8238
- XADD will start a new listpack if the new record may cause the previous
3a8238
  listpack to grow over 1GB.
3a8238
- XADD will respond with an error if a single stream record is over 1GB
3a8238
- List type (ziplist in quicklist) was truncating strings that were over 4GB,
3a8238
  now it'll respond with an error.
3a8238
3a8238
(cherry picked from commit 68e221a3f98a427805d31c1760b4cdf37ba810ab)
3a8238
---
3a8238
 src/geo.c                 |   5 +-
3a8238
 src/listpack.c            |   2 +-
3a8238
 src/quicklist.c           |  17 ++++-
3a8238
 src/rdb.c                 |  36 ++++++---
3a8238
 src/server.h              |   2 +-
3a8238
 src/t_hash.c              |  13 +++-
3a8238
 src/t_list.c              |  25 ++++++
3a8238
 src/t_stream.c            |  48 +++++++++---
3a8238
 src/t_zset.c              |  43 +++++++----
3a8238
 src/ziplist.c             |  17 ++++-
3a8238
 src/ziplist.h             |   1 +
3a8238
 tests/support/util.tcl    |  21 +++++
3a8238
 tests/unit/violations.tcl | 156 ++++++++++++++++++++++++++++++++++++++
3a8238
 13 files changed, 338 insertions(+), 48 deletions(-)
3a8238
 create mode 100644 tests/unit/violations.tcl
3a8238
3a8238
diff --git a/src/geo.c b/src/geo.c
3a8238
index f1d3f18d46e7..b94fcc1b3d70 100644
3a8238
--- a/src/geo.c
3a8238
+++ b/src/geo.c
3a8238
@@ -635,7 +635,7 @@ void georadiusGeneric(client *c, int fla
3a8238
         robj *zobj;
3a8238
         zset *zs;
3a8238
         int i;
3a8238
-        size_t maxelelen = 0;
3a8238
+        size_t maxelelen = 0, totelelen = 0;
3a8238
 
3a8238
         if (returned_items) {
3a8238
             zobj = createZsetObject();
3a8238
@@ -650,13 +650,14 @@ void georadiusGeneric(client *c, int fla
3a8238
             size_t elelen = sdslen(gp->member);
3a8238
 
3a8238
             if (maxelelen < elelen) maxelelen = elelen;
3a8238
+            totelelen += elelen;
3a8238
             znode = zslInsert(zs->zsl,score,gp->member);
3a8238
             serverAssert(dictAdd(zs->dict,gp->member,&znode->score) == DICT_OK);
3a8238
             gp->member = NULL;
3a8238
         }
3a8238
 
3a8238
         if (returned_items) {
3a8238
-            zsetConvertToZiplistIfNeeded(zobj,maxelelen);
3a8238
+            zsetConvertToZiplistIfNeeded(zobj,maxelelen,totelelen);
3a8238
             setKey(c->db,storekey,zobj);
3a8238
             decrRefCount(zobj);
3a8238
             notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
3a8238
diff --git a/src/listpack.c b/src/listpack.c
3a8238
index e1f4d9a02ee8..cd5583ccb258 100644
3a8238
--- a/src/listpack.c
3a8238
+++ b/src/listpack.c
3a8238
@@ -283,7 +283,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
3a8238
     } else {
3a8238
         if (size < 64) *enclen = 1+size;
3a8238
         else if (size < 4096) *enclen = 2+size;
3a8238
-        else *enclen = 5+size;
3a8238
+        else *enclen = 5+(uint64_t)size;
3a8238
         return LP_ENCODING_STRING;
3a8238
     }
3a8238
 }
3a8238
diff --git a/src/quicklist.c b/src/quicklist.c
3a8238
index 7b5484116785..d5cc758b2fa0 100644
3a8238
--- a/src/quicklist.c
3a8238
+++ b/src/quicklist.c
3a8238
@@ -29,6 +29,7 @@
3a8238
  */
3a8238
 
3a8238
 #include <string.h> /* for memcpy */
3a8238
+#include "redisassert.h"
3a8238
 #include "quicklist.h"
3a8238
 #include "zmalloc.h"
3a8238
 #include "ziplist.h"
3a8238
@@ -43,11 +44,16 @@
3a8238
 #define REDIS_STATIC static
3a8238
 #endif
3a8238
 
3a8238
-/* Optimization levels for size-based filling */
3a8238
+/* Optimization levels for size-based filling.
3a8238
+ * Note that the largest possible limit is 16k, so even if each record takes
3a8238
+ * just one byte, it still won't overflow the 16 bit count field. */
3a8238
 static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
3a8238
 
3a8238
 /* Maximum size in bytes of any multi-element ziplist.
3a8238
- * Larger values will live in their own isolated ziplists. */
3a8238
+ * Larger values will live in their own isolated ziplists.
3a8238
+ * This is used only if we're limited by record count. when we're limited by
3a8238
+ * size, the maximum limit is bigger, but still safe.
3a8238
+ * 8k is a recommended / default size limit */
3a8238
 #define SIZE_SAFETY_LIMIT 8192
3a8238
 
3a8238
 /* Minimum ziplist size in bytes for attempting compression. */
3a8238
@@ -441,6 +447,8 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
3a8238
     unsigned int new_sz = node->sz + sz + ziplist_overhead;
3a8238
     if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
3a8238
         return 1;
3a8238
+    /* when we return 1 above we know that the limit is a size limit (which is
3a8238
+     * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
3a8238
     else if (!sizeMeetsSafetyLimit(new_sz))
3a8238
         return 0;
3a8238
     else if ((int)node->count < fill)
3a8238
@@ -460,6 +468,8 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
3a8238
     unsigned int merge_sz = a->sz + b->sz - 11;
3a8238
     if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
3a8238
         return 1;
3a8238
+    /* when we return 1 above we know that the limit is a size limit (which is
3a8238
+     * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
3a8238
     else if (!sizeMeetsSafetyLimit(merge_sz))
3a8238
         return 0;
3a8238
     else if ((int)(a->count + b->count) <= fill)
3a8238
@@ -479,6 +489,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
3a8238
  * Returns 1 if new head created. */
3a8238
 int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
3a8238
     quicklistNode *orig_head = quicklist->head;
3a8238
+    assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
3a8238
     if (likely(
3a8238
             _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
3a8238
         quicklist->head->zl =
3a8238
@@ -502,6 +513,7 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
3a8238
  * Returns 1 if new tail created. */
3a8238
 int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
3a8238
     quicklistNode *orig_tail = quicklist->tail;
3a8238
+    assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
3a8238
     if (likely(
3a8238
             _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
3a8238
         quicklist->tail->zl =
3a8238
@@ -835,6 +847,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
3a8238
     int fill = quicklist->fill;
3a8238
     quicklistNode *node = entry->node;
3a8238
     quicklistNode *new_node = NULL;
3a8238
+    assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
3a8238
 
3a8238
     if (!node) {
3a8238
         /* we have no reference node, so let's create only node in the list */
3a8238
diff --git a/src/rdb.c b/src/rdb.c
3a8238
index 3c58a1eaf7fb..c7dc724f3df6 100644
3a8238
--- a/src/rdb.c
3a8238
+++ b/src/rdb.c
3a8238
@@ -1452,7 +1452,7 @@ robj *rdbLoadObject(int rdbtype, rio *rd
3a8238
     } else if (rdbtype == RDB_TYPE_ZSET_2 || rdbtype == RDB_TYPE_ZSET) {
3a8238
         /* Read list/set value. */
3a8238
         uint64_t zsetlen;
3a8238
-        size_t maxelelen = 0;
3a8238
+        size_t maxelelen = 0, totelelen = 0;
3a8238
         zset *zs;
3a8238
 
3a8238
         if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
3a8238
@@ -1479,6 +1479,7 @@ robj *rdbLoadObject(int rdbtype, rio *rd
3a8238
 
3a8238
             /* Don't care about integer-encoded strings. */
3a8238
             if (sdslen(sdsele) > maxelelen) maxelelen = sdslen(sdsele);
3a8238
+            totelelen += sdslen(sdsele);
3a8238
 
3a8238
             znode = zslInsert(zs->zsl,score,sdsele);
3a8238
             dictAdd(zs->dict,sdsele,&znode->score);
3a8238
@@ -1486,8 +1487,11 @@ robj *rdbLoadObject(int rdbtype, rio *rd
3a8238
 
3a8238
         /* Convert *after* loading, since sorted sets are not stored ordered. */
3a8238
         if (zsetLength(o) <= server.zset_max_ziplist_entries &&
3a8238
-            maxelelen <= server.zset_max_ziplist_value)
3a8238
-                zsetConvert(o,OBJ_ENCODING_ZIPLIST);
3a8238
+            maxelelen <= server.zset_max_ziplist_value &&
3a8238
+            ziplistSafeToAdd(NULL, totelelen))
3a8238
+        {
3a8238
+            zsetConvert(o,OBJ_ENCODING_ZIPLIST);
3a8238
+        }
3a8238
     } else if (rdbtype == RDB_TYPE_HASH) {
3a8238
         uint64_t len;
3a8238
         int ret;
3a8238
@@ -1511,21 +1515,25 @@ robj *rdbLoadObject(int rdbtype, rio *rd
3a8238
             if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
3a8238
                 == NULL) return NULL;
3a8238
 
3a8238
-            /* Add pair to ziplist */
3a8238
-            o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
3a8238
-                    sdslen(field), ZIPLIST_TAIL);
3a8238
-            o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
3a8238
-                    sdslen(value), ZIPLIST_TAIL);
3a8238
-
3a8238
             /* Convert to hash table if size threshold is exceeded */
3a8238
             if (sdslen(field) > server.hash_max_ziplist_value ||
3a8238
-                sdslen(value) > server.hash_max_ziplist_value)
3a8238
+                sdslen(value) > server.hash_max_ziplist_value ||
3a8238
+                !ziplistSafeToAdd(o->ptr, sdslen(field)+sdslen(value)))
3a8238
             {
3a8238
-                sdsfree(field);
3a8238
-                sdsfree(value);
3a8238
                 hashTypeConvert(o, OBJ_ENCODING_HT);
3a8238
+                ret = dictAdd((dict*)o->ptr, field, value);
3a8238
+                if (ret == DICT_ERR) {
3a8238
+                    rdbExitReportCorruptRDB("Duplicate hash fields detected");
3a8238
+                }
3a8238
                 break;
3a8238
             }
3a8238
+
3a8238
+            /* Add pair to ziplist */
3a8238
+            o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
3a8238
+                    sdslen(field), ZIPLIST_TAIL);
3a8238
+            o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
3a8238
+                    sdslen(value), ZIPLIST_TAIL);
3a8238
+
3a8238
             sdsfree(field);
3a8238
             sdsfree(value);
3a8238
         }
3a8238
@@ -1594,6 +1602,10 @@ robj *rdbLoadObject(int rdbtype, rio *rd
3a8238
                     while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
3a8238
                         if (flen > maxlen) maxlen = flen;
3a8238
                         if (vlen > maxlen) maxlen = vlen;
3a8238
+                        if (!ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
3a8238
+                            rdbExitReportCorruptRDB("Hash zipmap too big (%u)", flen);
3a8238
+                        }
3a8238
+
3a8238
                         zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
3a8238
                         zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
3a8238
                     }
3a8238
diff --git a/src/server.h b/src/server.h
3a8238
index ca868939cf6d..164a82271f44 100644
3a8238
--- a/src/server.h
3a8238
+++ b/src/server.h
3a8238
@@ -1677,7 +1677,7 @@ unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
3a8238
 unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range);
3a8238
 unsigned long zsetLength(const robj *zobj);
3a8238
 void zsetConvert(robj *zobj, int encoding);
3a8238
-void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
3a8238
+void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen);
3a8238
 int zsetScore(robj *zobj, sds member, double *score);
3a8238
 unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
3a8238
 int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore);
3a8238
diff --git a/src/t_hash.c b/src/t_hash.c
3a8238
index 0ca152df78cc..109522c1322f 100644
3a8238
--- a/src/t_hash.c
3a8238
+++ b/src/t_hash.c
3a8238
@@ -39,17 +39,22 @@
3a8238
  * as their string length can be queried in constant time. */
3a8238
 void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
3a8238
     int i;
3a8238
+    size_t sum = 0;
3a8238
 
3a8238
     if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
3a8238
 
3a8238
     for (i = start; i <= end; i++) {
3a8238
-        if (sdsEncodedObject(argv[i]) &&
3a8238
-            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
3a8238
-        {
3a8238
+        if (!sdsEncodedObject(argv[i]))
3a8238
+            continue;
3a8238
+        size_t len = sdslen(argv[i]->ptr);
3a8238
+        if (len > server.hash_max_ziplist_value) {
3a8238
             hashTypeConvert(o, OBJ_ENCODING_HT);
3a8238
-            break;
3a8238
+            return;
3a8238
         }
3a8238
+        sum += len;
3a8238
     }
3a8238
+    if (!ziplistSafeToAdd(o->ptr, sum))
3a8238
+        hashTypeConvert(o, OBJ_ENCODING_HT);
3a8238
 }
3a8238
 
3a8238
 /* Get the value from a ziplist encoded hash, identified by field.
3a8238
diff --git a/src/t_list.c b/src/t_list.c
3a8238
index de417f4705f4..67541554f616 100644
3a8238
--- a/src/t_list.c
3a8238
+++ b/src/t_list.c
3a8238
@@ -29,6 +29,8 @@
3a8238
 
3a8238
 #include "server.h"
3a8238
 
3a8238
+#define LIST_MAX_ITEM_SIZE ((1ull<<32)-1024)
3a8238
+
3a8238
 /*-----------------------------------------------------------------------------
3a8238
  * List API
3a8238
  *----------------------------------------------------------------------------*/
3a8238
@@ -196,6 +198,14 @@ void listTypeConvert(robj *subject, int enc) {
3a8238
 
3a8238
 void pushGenericCommand(client *c, int where) {
3a8238
     int j, pushed = 0;
3a8238
+
3a8238
+    for (j = 2; j < c->argc; j++) {
3a8238
+        if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
3a8238
+            addReplyError(c, "Element too large");
3a8238
+            return;
3a8238
+        }
3a8238
+    }
3a8238
+
3a8238
     robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
3a8238
 
3a8238
     if (lobj && lobj->type != OBJ_LIST) {
3a8238
@@ -277,6 +287,11 @@ void linsertCommand(client *c) {
3a8238
         return;
3a8238
     }
3a8238
 
3a8238
+    if (sdslen(c->argv[4]->ptr) > LIST_MAX_ITEM_SIZE) {
3a8238
+        addReplyError(c, "Element too large");
3a8238
+        return;
3a8238
+    }
3a8238
+
3a8238
     if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
3a8238
         checkType(c,subject,OBJ_LIST)) return;
3a8238
 
3a8238
@@ -344,6 +359,11 @@ void lsetCommand(client *c) {
3a8238
     long index;
3a8238
     robj *value = c->argv[3];
3a8238
 
3a8238
+    if (sdslen(value->ptr) > LIST_MAX_ITEM_SIZE) {
3a8238
+        addReplyError(c, "Element too large");
3a8238
+        return;
3a8238
+    }
3a8238
+
3a8238
     if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
3a8238
         return;
3a8238
 
3a8238
@@ -493,6 +513,11 @@ void lremCommand(client *c) {
3a8238
     long toremove;
3a8238
     long removed = 0;
3a8238
 
3a8238
+    if (sdslen(obj->ptr) > LIST_MAX_ITEM_SIZE) {
3a8238
+        addReplyError(c, "Element too large");
3a8238
+        return;
3a8238
+    }
3a8238
+
3a8238
     if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != C_OK))
3a8238
         return;
3a8238
 
3a8238
diff --git a/src/t_stream.c b/src/t_stream.c
3a8238
index d7754985dd03..e7263d68a28f 100644
3a8238
--- a/src/t_stream.c
3a8238
+++ b/src/t_stream.c
3a8238
@@ -40,6 +40,12 @@
3a8238
 #define STREAM_ITEM_FLAG_DELETED (1<<0)     /* Entry is delted. Skip it. */
3a8238
 #define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1)  /* Same fields as master entry. */
3a8238
 
3a8238
+/* Don't let listpacks grow too big, even if the user config allows it.
3a8238
+ * doing so can lead to an overflow (trying to store more than 32bit length
3a8238
+ * into the listpack header), or actually an assertion since lpInsert
3a8238
+ * will return NULL. */
3a8238
+#define STREAM_LISTPACK_MAX_SIZE (1<<30)
3a8238
+
3a8238
 void streamFreeCG(streamCG *cg);
3a8238
 void streamFreeNACK(streamNACK *na);
3a8238
 size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer);
3a8238
@@ -170,12 +176,31 @@ int streamCompareID(streamID *a, streamI
3a8238
  *
3a8238
  * The function returns C_OK if the item was added, this is always true
3a8238
  * if the ID was generated by the function. However the function may return
3a8238
- * C_ERR if an ID was given via 'use_id', but adding it failed since the
3a8238
- * current top ID is greater or equal. */
3a8238
+ * C_ERR in several cases:
3a8238
+ * 1. If an ID was given via 'use_id', but adding it failed since the
3a8238
+ *    current top ID is greater or equal. errno will be set to EDOM.
3a8238
+ * 2. If a size of a single element or the sum of the elements is too big to
3a8238
+ *    be stored into the stream. errno will be set to ERANGE. */
3a8238
 int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
3a8238
     /* If an ID was given, check that it's greater than the last entry ID
3a8238
      * or return an error. */
3a8238
-    if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
3a8238
+    if (use_id && streamCompareID(use_id,&s->last_id) <= 0) {
3a8238
+        errno = EDOM;
3a8238
+        return C_ERR;
3a8238
+    }
3a8238
+
3a8238
+    /* Avoid overflow when trying to add an element to the stream (listpack
3a8238
+     * can only host up to 32bit length sttrings, and also a total listpack size
3a8238
+     * can't be bigger than 32bit length. */
3a8238
+    size_t totelelen = 0;
3a8238
+    for (int64_t i = 0; i < numfields*2; i++) {
3a8238
+        sds ele = argv[i]->ptr;
3a8238
+        totelelen += sdslen(ele);
3a8238
+    }
3a8238
+    if (totelelen > STREAM_LISTPACK_MAX_SIZE) {
3a8238
+        errno = ERANGE;
3a8238
+        return C_ERR;
3a8238
+    }
3a8238
 
3a8238
     /* Add the new entry. */
3a8238
     raxIterator ri;
3a8238
@@ -241,9 +266,10 @@ int streamAppendItem(stream *s, robj **a
3a8238
      * if we need to switch to the next one. 'lp' will be set to NULL if
3a8238
      * the current node is full. */
3a8238
     if (lp != NULL) {
3a8238
-        if (server.stream_node_max_bytes &&
3a8238
-            lp_bytes > server.stream_node_max_bytes)
3a8238
-        {
3a8238
+        size_t node_max_bytes = server.stream_node_max_bytes;
3a8238
+        if (node_max_bytes == 0 || node_max_bytes > STREAM_LISTPACK_MAX_SIZE)
3a8238
+            node_max_bytes = STREAM_LISTPACK_MAX_SIZE;
3a8238
+        if (lp_bytes + totelelen >= node_max_bytes) {
3a8238
             lp = NULL;
3a8238
         } else if (server.stream_node_max_entries) {
3a8238
             int64_t count = lpGetInteger(lpFirst(lp));
3a8238
@@ -1224,11 +1250,13 @@ void xaddCommand(client *c) {
3a8238
 
3a8238
     /* Append using the low level function and return the ID. */
3a8238
     if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
3a8238
-        &id, id_given ? &id : NULL)
3a8238
-        == C_ERR)
3a8238
+        &id, id_given ? &id : NULL) == C_ERR)
3a8238
     {
3a8238
-        addReplyError(c,"The ID specified in XADD is equal or smaller than the "
3a8238
-                        "target stream top item");
3a8238
+        if (errno == EDOM)
3a8238
+            addReplyError(c,"The ID specified in XADD is equal or smaller than "
3a8238
+                            "the target stream top item");
3a8238
+        else
3a8238
+            addReplyError(c,"Elements are too large to be stored");
3a8238
         return;
3a8238
     }
3a8238
     addReplyStreamID(c,&id;;
3a8238
diff --git a/src/t_zset.c b/src/t_zset.c
3a8238
index 56ea39607b52..989d5855e1ea 100644
3a8238
--- a/src/t_zset.c
3a8238
+++ b/src/t_zset.c
3a8238
@@ -1237,15 +1237,18 @@ void zsetConvert(robj *zobj, int encodin
3a8238
 }
3a8238
 
3a8238
 /* Convert the sorted set object into a ziplist if it is not already a ziplist
3a8238
- * and if the number of elements and the maximum element size is within the
3a8238
- * expected ranges. */
3a8238
-void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
3a8238
+ * and if the number of elements and the maximum element size and total elements size
3a8238
+ * are within the expected ranges. */
3a8238
+void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {
3a8238
     if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
3a8238
     zset *zset = zobj->ptr;
3a8238
 
3a8238
     if (zset->zsl->length <= server.zset_max_ziplist_entries &&
3a8238
-        maxelelen <= server.zset_max_ziplist_value)
3a8238
-            zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
3a8238
+        maxelelen <= server.zset_max_ziplist_value &&
3a8238
+        ziplistSafeToAdd(NULL, totelelen))
3a8238
+    {
3a8238
+        zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
3a8238
+    }
3a8238
 }
3a8238
 
3a8238
 /* Return (by reference) the score of the specified member of the sorted set
3a8238
@@ -1354,21 +1357,28 @@ int zsetAdd(robj *zobj, double score, sd
3a8238
             }
3a8238
             return 1;
3a8238
         } else if (!xx) {
3a8238
-            /* Optimize: check if the element is too large or the list
3a8238
+            /* check if the element is too large or the list
3a8238
              * becomes too long *before* executing zzlInsert. */
3a8238
-            zobj->ptr = zzlInsert(zobj->ptr,ele,score);
3a8238
-            if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
3a8238
-                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
3a8238
-            if (sdslen(ele) > server.zset_max_ziplist_value)
3a8238
+            if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
3a8238
+                sdslen(ele) > server.zset_max_ziplist_value ||
3a8238
+                !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
3a8238
+            {
3a8238
                 zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
3a8238
-            if (newscore) *newscore = score;
3a8238
-            *flags |= ZADD_ADDED;
3a8238
-            return 1;
3a8238
+            } else {
3a8238
+                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
3a8238
+                if (newscore) *newscore = score;
3a8238
+                *flags |= ZADD_ADDED;
3a8238
+                return 1;
3a8238
+            }
3a8238
         } else {
3a8238
             *flags |= ZADD_NOP;
3a8238
             return 1;
3a8238
         }
3a8238
-    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
3a8238
+    }
3a8238
+
3a8238
+    /* Note that the above block handling ziplist would have either returned or
3a8238
+     * converted the key to skiplist. */
3a8238
+    if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
3a8238
         zset *zs = zobj->ptr;
3a8238
         zskiplistNode *znode;
3a8238
         dictEntry *de;
3a8238
@@ -2180,7 +2190,7 @@ void zunionInterGenericCommand(client *c
3a8238
     zsetopsrc *src;
3a8238
     zsetopval zval;
3a8238
     sds tmp;
3a8238
-    size_t maxelelen = 0;
3a8238
+    size_t maxelelen = 0, totelelen = 0;
3a8238
     robj *dstobj;
3a8238
     zset *dstzset;
3a8238
     zskiplistNode *znode;
3a8238
@@ -2304,6 +2314,7 @@ void zunionInterGenericCommand(client *c
3a8238
                     tmp = zuiNewSdsFromValue(&zval);
3a8238
                     znode = zslInsert(dstzset->zsl,score,tmp);
3a8238
                     dictAdd(dstzset->dict,tmp,&znode->score);
3a8238
+                    totelelen += sdslen(tmp);
3a8238
                     if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
3a8238
                 }
3a8238
             }
3a8238
@@ -2340,6 +2351,7 @@ void zunionInterGenericCommand(client *c
3a8238
                     /* Remember the longest single element encountered,
3a8238
                      * to understand if it's possible to convert to ziplist
3a8238
                      * at the end. */
3a8238
+                     totelelen += sdslen(tmp);
3a8238
                      if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
3a8238
                     /* Update the element with its initial score. */
3a8238
                     dictSetKey(accumulator, de, tmp);
3a8238
@@ -2380,7 +2392,7 @@ void zunionInterGenericCommand(client *c
3a8238
     if (dbDelete(c->db,dstkey))
3a8238
         touched = 1;
3a8238
     if (dstzset->zsl->length) {
3a8238
-        zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
3a8238
+        zsetConvertToZiplistIfNeeded(dstobj,maxelelen,totelelen);
3a8238
         dbAdd(c->db,dstkey,dstobj);
3a8238
         addReplyLongLong(c,zsetLength(dstobj));
3a8238
         signalModifiedKey(c->db,dstkey);
3a8238
diff --git a/src/ziplist.c b/src/ziplist.c
3a8238
index dbd804b11dfc..1a8566698972 100644
3a8238
--- a/src/ziplist.c
3a8238
+++ b/src/ziplist.c
3a8238
@@ -265,6 +265,17 @@
3a8238
         ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
3a8238
 }
3a8238
 
3a8238
+/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
3a8238
+ * zlbytes*/
3a8238
+#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
3a8238
+int ziplistSafeToAdd(unsigned char* zl, size_t add) {
3a8238
+    size_t len = zl? ziplistBlobLen(zl): 0;
3a8238
+    if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
3a8238
+        return 0;
3a8238
+    return 1;
3a8238
+}
3a8238
+
3a8238
+
3a8238
 /* We use this function to receive information about a ziplist entry.
3a8238
  * Note that this is not how the data is actually encoded, is just what we
3a8238
  * get filled by a function in order to operate more easily. */
3a8238
@@ -586,7 +597,8 @@ unsigned char *ziplistNew(void) {
3a8238
 }
3a8238
 
3a8238
 /* Resize the ziplist. */
3a8238
-unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
3a8238
+unsigned char *ziplistResize(unsigned char *zl, size_t len) {
3a8238
+    assert(len < UINT32_MAX);
3a8238
     zl = zrealloc(zl,len);
3a8238
     ZIPLIST_BYTES(zl) = intrev32ifbe(len);
3a8238
     zl[len-1] = ZIP_END;
3a8238
@@ -898,6 +910,9 @@ unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
3a8238
     /* Combined zl length should be limited within UINT16_MAX */
3a8238
     zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;
3a8238
 
3a8238
+    /* larger values can't be stored into ZIPLIST_BYTES */
3a8238
+    assert(zlbytes < UINT32_MAX);
3a8238
+
3a8238
     /* Save offset positions before we start ripping memory apart. */
3a8238
     size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
3a8238
     size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));
3a8238
diff --git a/src/ziplist.h b/src/ziplist.h
3a8238
index 964a47f6dc29..f6ba6c8be47d 100644
3a8238
--- a/src/ziplist.h
3a8238
+++ b/src/ziplist.h
3a8238
@@ -49,6 +49,7 @@ unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int v
3a8238
 unsigned int ziplistLen(unsigned char *zl);
3a8238
 size_t ziplistBlobLen(unsigned char *zl);
3a8238
 void ziplistRepr(unsigned char *zl);
3a8238
+int ziplistSafeToAdd(unsigned char* zl, size_t add);
3a8238
 
3a8238
 #ifdef REDIS_TEST
3a8238
 int ziplistTest(int argc, char *argv[]);
3a8238
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
3a8238
index 74f491e483a5..46b56cc2822a 100644
3a8238
--- a/tests/support/util.tcl
3a8238
+++ b/tests/support/util.tcl
3a8238
@@ -99,6 +99,27 @@ proc wait_for_ofs_sync {r1 r2} {
3a8238
     }
3a8238
 }
3a8238
 
3a8238
+# count current log lines in server's stdout
3a8238
+proc count_log_lines {srv_idx} {
3a8238
+    set _ [string trim [exec wc -l < [srv $srv_idx stdout]]]
3a8238
+}
3a8238
+
3a8238
+# returns the number of times a line with that pattern appears in a file
3a8238
+proc count_message_lines {file pattern} {
3a8238
+    set res 0
3a8238
+    # exec fails when grep exists with status other than 0 (when the patter wasn't found)
3a8238
+    catch {
3a8238
+        set res [string trim [exec grep $pattern $file 2> /dev/null | wc -l]]
3a8238
+    }
3a8238
+    return $res
3a8238
+}
3a8238
+
3a8238
+# returns the number of times a line with that pattern appears in the log
3a8238
+proc count_log_message {srv_idx pattern} {
3a8238
+    set stdout [srv $srv_idx stdout]
3a8238
+    return [count_message_lines $stdout $pattern]
3a8238
+}
3a8238
+
3a8238
 # Random integer between 0 and max (excluded).
3a8238
 proc randomInt {max} {
3a8238
     expr {int(rand()*$max)}
3a8238
diff --git a/tests/unit/violations.tcl b/tests/unit/violations.tcl
3a8238
new file mode 100644
3a8238
index 000000000000..d87b9236528e
3a8238
--- /dev/null
3a8238
+++ b/tests/unit/violations.tcl
3a8238
@@ -0,0 +1,156 @@
3a8238
+# These tests consume massive amounts of memory, and are not
3a8238
+# suitable to be executed as part of the normal test suite
3a8238
+set ::str500 [string repeat x 500000000] ;# 500mb
3a8238
+
3a8238
+# Utility function to write big argument into redis client connection
3a8238
+proc write_big_bulk {size} {
3a8238
+    r write "\$$size\r\n"
3a8238
+    while {$size >= 500000000} {
3a8238
+        r write $::str500
3a8238
+        incr size -500000000
3a8238
+    }
3a8238
+    if {$size > 0} {
3a8238
+        r write [string repeat x $size]
3a8238
+    }
3a8238
+    r write "\r\n"
3a8238
+}
3a8238
+
3a8238
+# One XADD with one huge 5GB field
3a8238
+# Expected to fail resulting in an empty stream
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {XADD one huge field} {
3a8238
+        r config set proto-max-bulk-len 10000000000 ;#10gb
3a8238
+        r config set client-query-buffer-limit 10000000000 ;#10gb
3a8238
+        r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n"
3a8238
+        r write "\$1\r\nA\r\n"
3a8238
+        write_big_bulk 5000000000 ;#5gb
3a8238
+        r flush
3a8238
+        catch {r read} err
3a8238
+        assert_match {*too large*} $err
3a8238
+        r xlen S1
3a8238
+    } {0}
3a8238
+}
3a8238
+
3a8238
+# One XADD with one huge (exactly nearly) 4GB field
3a8238
+# This uncovers the overflow in lpEncodeGetType
3a8238
+# Expected to fail resulting in an empty stream
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {XADD one huge field - 1} {
3a8238
+        r config set proto-max-bulk-len 10000000000 ;#10gb
3a8238
+        r config set client-query-buffer-limit 10000000000 ;#10gb
3a8238
+        r write "*5\r\n\$4\r\nXADD\r\n\$2\r\nS1\r\n\$1\r\n*\r\n"
3a8238
+        r write "\$1\r\nA\r\n"
3a8238
+        write_big_bulk 4294967295 ;#4gb-1
3a8238
+        r flush
3a8238
+        catch {r read} err
3a8238
+        assert_match {*too large*} $err
3a8238
+        r xlen S1
3a8238
+    } {0}
3a8238
+}
3a8238
+
3a8238
+# Gradually add big stream fields using repeated XADD calls
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {several XADD big fields} {
3a8238
+        r config set stream-node-max-bytes 0
3a8238
+        for {set j 0} {$j<10} {incr j} {
3a8238
+            r xadd stream * 1 $::str500 2 $::str500
3a8238
+        }
3a8238
+        r ping
3a8238
+        r xlen stream
3a8238
+    } {10}
3a8238
+}
3a8238
+
3a8238
+# Add over 4GB to a single stream listpack (one XADD command)
3a8238
+# Expected to fail resulting in an empty stream
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {single XADD big fields} {
3a8238
+        r write "*23\r\n\$4\r\nXADD\r\n\$1\r\nS\r\n\$1\r\n*\r\n"
3a8238
+        for {set j 0} {$j<10} {incr j} {
3a8238
+            r write "\$1\r\n$j\r\n"
3a8238
+            write_big_bulk 500000000 ;#500mb
3a8238
+        }
3a8238
+        r flush
3a8238
+        catch {r read} err
3a8238
+        assert_match {*too large*} $err
3a8238
+        r xlen S
3a8238
+    } {0}
3a8238
+}
3a8238
+
3a8238
+# Gradually add big hash fields using repeated HSET calls
3a8238
+# This reproduces the overflow in the call to ziplistResize
3a8238
+# Object will be converted to hashtable encoding
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    r config set hash-max-ziplist-value 1000000000 ;#1gb
3a8238
+    test {hash with many big fields} {
3a8238
+        for {set j 0} {$j<10} {incr j} {
3a8238
+            r hset h $j $::str500
3a8238
+        }
3a8238
+        r object encoding h
3a8238
+    } {hashtable}
3a8238
+}
3a8238
+
3a8238
+# Add over 4GB to a single hash field (one HSET command)
3a8238
+# Object will be converted to hashtable encoding
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {hash with one huge field} {
3a8238
+        catch {r config set hash-max-ziplist-value 10000000000} ;#10gb
3a8238
+        r config set proto-max-bulk-len 10000000000 ;#10gb
3a8238
+        r config set client-query-buffer-limit 10000000000 ;#10gb
3a8238
+        r write "*4\r\n\$4\r\nHSET\r\n\$2\r\nH1\r\n"
3a8238
+        r write "\$1\r\nA\r\n"
3a8238
+        write_big_bulk 5000000000 ;#5gb
3a8238
+        r flush
3a8238
+        r read
3a8238
+        r object encoding H1
3a8238
+    } {hashtable}
3a8238
+}
3a8238
+
3a8238
+# Add over 4GB to a single list member (one LPUSH command)
3a8238
+# Currently unsupported, and expected to fail rather than being truncated
3a8238
+# Expected to fail resulting in a non-existing list
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {list with one huge field} {
3a8238
+        r config set proto-max-bulk-len 10000000000 ;#10gb
3a8238
+        r config set client-query-buffer-limit 10000000000 ;#10gb
3a8238
+        r write "*3\r\n\$5\r\nLPUSH\r\n\$2\r\nL1\r\n"
3a8238
+        write_big_bulk 5000000000 ;#5gb
3a8238
+        r flush
3a8238
+        catch {r read} err
3a8238
+        assert_match {*too large*} $err
3a8238
+        r exists L1
3a8238
+    } {0}
3a8238
+}
3a8238
+
3a8238
+# SORT which attempts to store an element larger than 4GB into a list.
3a8238
+# Currently unsupported and results in an assertion instead of truncation
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {SORT adds huge field to list} {
3a8238
+        r config set proto-max-bulk-len 10000000000 ;#10gb
3a8238
+        r config set client-query-buffer-limit 10000000000 ;#10gb
3a8238
+        r write "*3\r\n\$3\r\nSET\r\n\$2\r\nS1\r\n"
3a8238
+        write_big_bulk 5000000000 ;#5gb
3a8238
+        r flush
3a8238
+        r read
3a8238
+        assert_equal [r strlen S1] 5000000000
3a8238
+        r set S2 asdf
3a8238
+        r sadd myset 1 2
3a8238
+        r mset D1 1 D2 2
3a8238
+        catch {r sort myset by D* get S* store mylist}
3a8238
+        # assert_equal [count_log_message 0 "crashed by signal"] 0   - not suitable for 6.0
3a8238
+        assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
3a8238
+    }
3a8238
+}
3a8238
+
3a8238
+# SORT which stores an integer encoded element into a list.
3a8238
+# Just for coverage, no news here.
3a8238
+start_server [list overrides [list save ""] ] {
3a8238
+    test {SORT adds integer field to list} {
3a8238
+        r set S1 asdf
3a8238
+        r set S2 123 ;# integer encoded
3a8238
+        assert_encoding "int" S2
3a8238
+        r sadd myset 1 2
3a8238
+        r mset D1 1 D2 2
3a8238
+        r sort myset by D* get S* store mylist
3a8238
+        r llen mylist
3a8238
+    } {2}
3a8238
+}