diff options
author | Oran Agra <oran@redislabs.com> | 2021-10-04 12:11:02 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-04 12:11:02 +0300 |
commit | c5e6a6204c4cf57f85e7c83a9b4e99f1a7204fd2 (patch) | |
tree | 9f55ffb0f03b07391b4796331aabcb7881ba80ae /src/t_zset.c | |
parent | fba15850e5c31666e4c3560a3be7fd034fa7e2b6 (diff) | |
download | redis-c5e6a6204c4cf57f85e7c83a9b4e99f1a7204fd2.tar.gz |
Fix ziplist and listpack overflows and truncations (CVE-2021-32627, CVE-2021-32628) (#9589)
- 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.
Co-authored-by: sundb <sundbcn@gmail.com>
Diffstat (limited to 'src/t_zset.c')
-rw-r--r-- | src/t_zset.c | 62 |
1 files changed, 39 insertions, 23 deletions
diff --git a/src/t_zset.c b/src/t_zset.c index a6bfddf46..157ff84c5 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -1227,15 +1227,18 @@ void zsetConvert(robj *zobj, int encoding) { } /* Convert the sorted set object into a listpack if it is not already a listpack - * and if the number of elements and the maximum element size is within the - * expected ranges. */ -void zsetConvertToListpackIfNeeded(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 zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) { if (zobj->encoding == OBJ_ENCODING_LISTPACK) return; zset *zset = zobj->ptr; if (zset->zsl->length <= server.zset_max_listpack_entries && - maxelelen <= server.zset_max_listpack_value) - zsetConvert(zobj,OBJ_ENCODING_LISTPACK); + maxelelen <= server.zset_max_listpack_value && + lpSafeToAdd(NULL, totelelen)) + { + zsetConvert(zobj,OBJ_ENCODING_LISTPACK); + } } /* 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 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_listpack_entries || - sdslen(ele) > server.zset_max_listpack_value) + if (zzlLength(zobj->ptr)+1 > server.zset_max_listpack_entries || + sdslen(ele) > server.zset_max_listpack_value || + !lpSafeToAdd(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; @@ -2304,7 +2315,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; @@ -2314,6 +2325,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); @@ -2321,7 +2334,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, @@ -2369,13 +2382,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. @@ -2429,7 +2443,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) { @@ -2466,14 +2480,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"); } @@ -2510,7 +2524,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; @@ -2668,6 +2682,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); } } @@ -2704,6 +2719,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 listpack * at the end. */ + totelelen += sdslen(tmp); if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp); /* Update the element with its initial score. */ dictSetKey(accumulator, de, tmp); @@ -2738,14 +2754,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) { - zsetConvertToListpackIfNeeded(dstobj, maxelelen); + zsetConvertToListpackIfNeeded(dstobj, maxelelen, totelelen); setKey(c, c->db, dstkey, dstobj); addReplyLongLong(c, zsetLength(dstobj)); notifyKeyspaceEvent(NOTIFY_ZSET, |