diffstat for redis-7.0.10 redis-7.0.11 00-RELEASENOTES | 23 ++ debian/changelog | 16 + debian/patches/0004-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch | 10 - src/acl.c | 7 src/aof.c | 31 +-- src/debug.c | 2 src/listpack_malloc.h | 7 src/module.c | 18 + src/networking.c | 13 - src/server.c | 1 src/server.h | 3 src/t_hash.c | 4 src/version.h | 4 src/zmalloc.c | 96 ++++++++-- src/zmalloc.h | 15 + tests/integration/corrupt-dump.tcl | 11 + tests/unit/acl.tcl | 14 + tests/unit/type/hash.tcl | 7 18 files changed, 230 insertions(+), 52 deletions(-) diff -Nru redis-7.0.10/00-RELEASENOTES redis-7.0.11/00-RELEASENOTES --- redis-7.0.10/00-RELEASENOTES 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/00-RELEASENOTES 2023-04-17 05:54:03.000000000 -0700 @@ -11,6 +11,29 @@ SECURITY: There are security fixes in the release. -------------------------------------------------------------------------------- + +================================================================================ +Redis 7.0.11 Released Mon Apr 17 16:00:00 IST 2023 +================================================================================ + +Upgrade urgency: SECURITY, contains fixes to security issues. + +Security Fixes: +* (CVE-2023-28856) Authenticated users can use the HINCRBYFLOAT command to create + an invalid hash field that will crash Redis on access + +Bug Fixes +========= + +* Add a missing fsync of AOF file in rare cases (#11973) +* Disconnect pub-sub subscribers when revoking allchannels permission (#11992) + +Platform / toolchain support related improvements +================================================= + +* Fix a compiler fortification induced crash when used with link time optimizations (#11982) + + ================================================================================ Redis 7.0.10 Released Mon Mar 20 16:00:00 IST 2023 ================================================================================ diff -Nru redis-7.0.10/debian/changelog redis-7.0.11/debian/changelog --- redis-7.0.10/debian/changelog 2023-03-25 06:04:38.000000000 -0700 +++ redis-7.0.11/debian/changelog 2023-04-19 23:38:23.000000000 -0700 @@ -1,3 +1,19 @@ +redis (5:7.0.11-1) unstable; urgency=high + + * New upstream security release: + + - CVE-2023-28856: Authenticated users could have used the HINCRBYFLOAT + command to create an invalid hash field that would have crashed the Redis + server on access. (Closes: #1034613) + + For more information, please see: + + https://raw.githubusercontent.com/redis/redis/7.0/00-RELEASENOTES + + * Refresh patches. + + -- Chris Lamb Thu, 20 Apr 2023 07:38:23 +0100 + redis (5:7.0.10-1) unstable; urgency=medium * New upstream release. diff -Nru redis-7.0.10/debian/patches/0004-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch redis-7.0.11/debian/patches/0004-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch --- redis-7.0.10/debian/patches/0004-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch 2023-03-25 06:04:38.000000000 -0700 +++ redis-7.0.11/debian/patches/0004-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch 2023-04-19 23:38:23.000000000 -0700 @@ -48,7 +48,7 @@ ifeq ($(BUILD_TLS),yes) FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS) diff --git a/src/debug.c b/src/debug.c -index 80a95aa..e8ffe7d 100644 +index 38ee6fb..879f845 100644 --- a/src/debug.c +++ b/src/debug.c @@ -72,6 +72,10 @@ void printCrashReport(void); @@ -94,10 +94,10 @@ static inline int sdsHdrSize(char type) { diff --git a/src/zmalloc.c b/src/zmalloc.c -index 857baa8..d6c00db 100644 +index 0ace61f..fec8478 100644 --- a/src/zmalloc.c +++ b/src/zmalloc.c -@@ -81,6 +81,15 @@ void zlibc_free(void *ptr) { +@@ -79,6 +79,15 @@ void zlibc_free(void *ptr) { #define realloc(ptr,size) tc_realloc(ptr,size) #define free(ptr) tc_free(ptr) #elif defined(USE_JEMALLOC) @@ -113,7 +113,7 @@ #define malloc(size) je_malloc(size) #define calloc(count,size) je_calloc(count,size) #define realloc(ptr,size) je_realloc(ptr,size) -@@ -88,6 +97,7 @@ void zlibc_free(void *ptr) { +@@ -86,6 +95,7 @@ void zlibc_free(void *ptr) { #define mallocx(size,flags) je_mallocx(size,flags) #define dallocx(ptr,flags) je_dallocx(ptr,flags) #endif @@ -122,7 +122,7 @@ #define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n)) #define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n)) diff --git a/src/zmalloc.h b/src/zmalloc.h -index 2d133e4..49774e5 100644 +index efab1a1..7826053 100644 --- a/src/zmalloc.h +++ b/src/zmalloc.h @@ -50,7 +50,11 @@ diff -Nru redis-7.0.10/src/acl.c redis-7.0.11/src/acl.c --- redis-7.0.10/src/acl.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/acl.c 2023-04-17 05:54:03.000000000 -0700 @@ -1844,6 +1844,13 @@ listRewind(original->selectors,&li); while((ln = listNext(&li)) && match) { aclSelector *s = (aclSelector *) listNodeValue(ln); + /* If any of the original selectors has the all-channels permission, but + * the new ones don't (this is checked earlier in this function), then the + * new list is not a strict superset of the original. */ + if (s->flags & SELECTOR_FLAG_ALLCHANNELS) { + match = 0; + break; + } listRewind(s->channels, &lpi); while((lpn = listNext(&lpi)) && match) { if (!listSearchKey(upcoming, listNodeValue(lpn))) { diff -Nru redis-7.0.10/src/aof.c redis-7.0.11/src/aof.c --- redis-7.0.10/src/aof.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/aof.c 2023-04-17 05:54:03.000000000 -0700 @@ -758,6 +758,7 @@ } server.aof_last_incr_size = getAppendOnlyFileSize(aof_name, NULL); + server.aof_last_incr_fsync_offset = server.aof_last_incr_size; if (incr_aof_len) { serverLog(LL_NOTICE, "Opening AOF incr file %s on server start", aof_name); @@ -832,13 +833,14 @@ * is already synced at this point so fsync doesn't matter. */ if (server.aof_fd != -1) { aof_background_fsync_and_close(server.aof_fd); - server.aof_fsync_offset = server.aof_current_size; server.aof_last_fsync = server.unixtime; } server.aof_fd = newfd; /* Reset the aof_last_incr_size. */ server.aof_last_incr_size = 0; + /* Reset the aof_last_incr_fsync_offset. */ + server.aof_last_incr_fsync_offset = 0; /* Update `server.aof_manifest`. */ if (temp_am) aofManifestFreeAndUpdate(temp_am); return C_OK; @@ -952,7 +954,6 @@ if (redis_fsync(server.aof_fd) == -1) { serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno)); } else { - server.aof_fsync_offset = server.aof_current_size; server.aof_last_fsync = server.unixtime; } close(server.aof_fd); @@ -962,6 +963,7 @@ server.aof_state = AOF_OFF; server.aof_rewrite_scheduled = 0; server.aof_last_incr_size = 0; + server.aof_last_incr_fsync_offset = 0; killAppendOnlyChild(); sdsfree(server.aof_buf); server.aof_buf = sdsempty(); @@ -1069,10 +1071,19 @@ * stop write commands before fsync called in one second, * the data in page cache cannot be flushed in time. */ if (server.aof_fsync == AOF_FSYNC_EVERYSEC && - server.aof_fsync_offset != server.aof_current_size && + server.aof_last_incr_fsync_offset != server.aof_last_incr_size && server.unixtime > server.aof_last_fsync && !(sync_in_progress = aofFsyncInProgress())) { goto try_fsync; + + /* Check if we need to do fsync even the aof buffer is empty, + * the reason is described in the previous AOF_FSYNC_EVERYSEC block, + * and AOF_FSYNC_ALWAYS is also checked here to handle a case where + * aof_fsync is changed from everysec to always. */ + } else if (server.aof_fsync == AOF_FSYNC_ALWAYS && + server.aof_last_incr_fsync_offset != server.aof_last_incr_size) + { + goto try_fsync; } else { return; } @@ -1239,13 +1250,13 @@ } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fsync-always",latency); - server.aof_fsync_offset = server.aof_current_size; + server.aof_last_incr_fsync_offset = server.aof_last_incr_size; server.aof_last_fsync = server.unixtime; - } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && - server.unixtime > server.aof_last_fsync)) { + } else if (server.aof_fsync == AOF_FSYNC_EVERYSEC && + server.unixtime > server.aof_last_fsync) { if (!sync_in_progress) { aof_background_fsync(server.aof_fd); - server.aof_fsync_offset = server.aof_current_size; + server.aof_last_incr_fsync_offset = server.aof_last_incr_size; } server.aof_last_fsync = server.unixtime; } @@ -1739,7 +1750,6 @@ * executed early, but that shouldn't be a problem since everything will be * fine after the first AOFRW. */ server.aof_rewrite_base_size = base_size; - server.aof_fsync_offset = server.aof_current_size; cleanup: stopLoading(ret == AOF_OK || ret == AOF_TRUNCATED); @@ -2660,13 +2670,10 @@ /* We can safely let `server.aof_manifest` point to 'temp_am' and free the previous one. */ aofManifestFreeAndUpdate(temp_am); - if (server.aof_fd != -1) { + if (server.aof_state != AOF_OFF) { /* AOF enabled. */ - server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ server.aof_current_size = getAppendOnlyFileSize(new_base_filename, NULL) + server.aof_last_incr_size; server.aof_rewrite_base_size = server.aof_current_size; - server.aof_fsync_offset = server.aof_current_size; - server.aof_last_fsync = server.unixtime; } /* We don't care about the return value of `aofDelHistoryFiles`, because the history diff -Nru redis-7.0.10/src/debug.c redis-7.0.11/src/debug.c --- redis-7.0.10/src/debug.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/debug.c 2023-04-17 05:54:03.000000000 -0700 @@ -519,7 +519,7 @@ restartServer(flags,delay); addReplyError(c,"failed to restart the server. Check server logs."); } else if (!strcasecmp(c->argv[1]->ptr,"oom")) { - void *ptr = zmalloc(ULONG_MAX); /* Should trigger an out of memory. */ + void *ptr = zmalloc(SIZE_MAX/2); /* Should trigger an out of memory. */ zfree(ptr); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"assert")) { diff -Nru redis-7.0.10/src/listpack_malloc.h redis-7.0.11/src/listpack_malloc.h --- redis-7.0.10/src/listpack_malloc.h 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/listpack_malloc.h 2023-04-17 05:54:03.000000000 -0700 @@ -39,8 +39,11 @@ #ifndef LISTPACK_ALLOC_H #define LISTPACK_ALLOC_H #include "zmalloc.h" -#define lp_malloc zmalloc -#define lp_realloc zrealloc +/* We use zmalloc_usable/zrealloc_usable instead of zmalloc/zrealloc + * to ensure the safe invocation of 'zmalloc_usable_size(). + * See comment in zmalloc_usable_size(). */ +#define lp_malloc(sz) zmalloc_usable(sz,NULL) +#define lp_realloc(ptr,sz) zrealloc_usable(ptr,sz,NULL) #define lp_free zfree #define lp_malloc_size zmalloc_usable_size #endif diff -Nru redis-7.0.10/src/module.c redis-7.0.11/src/module.c --- redis-7.0.10/src/module.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/module.c 2023-04-17 05:54:03.000000000 -0700 @@ -474,13 +474,20 @@ * You should avoid using malloc(). * This function panics if unable to allocate enough memory. */ void *RM_Alloc(size_t bytes) { - return zmalloc(bytes); + /* Use 'zmalloc_usable()' instead of 'zmalloc()' to allow the compiler + * to recognize the additional memory size, which means that modules can + * use the memory reported by 'RM_MallocUsableSize()' safely. In theory this + * isn't really needed since this API can't be inlined (not even for embedded + * modules like TLS (we use function pointers for module APIs), and the API doesn't + * have the malloc_size attribute, but it's hard to predict how smart future compilers + * will be, so better safe than sorry. */ + return zmalloc_usable(bytes,NULL); } /* Similar to RM_Alloc, but returns NULL in case of allocation failure, instead * of panicking. */ void *RM_TryAlloc(size_t bytes) { - return ztrymalloc(bytes); + return ztrymalloc_usable(bytes,NULL); } /* Use like calloc(). Memory allocated with this function is reported in @@ -488,12 +495,12 @@ * and in general is taken into account as memory allocated by Redis. * You should avoid using calloc() directly. */ void *RM_Calloc(size_t nmemb, size_t size) { - return zcalloc(nmemb*size); + return zcalloc_usable(nmemb*size,NULL); } /* Use like realloc() for memory obtained with RedisModule_Alloc(). */ void* RM_Realloc(void *ptr, size_t bytes) { - return zrealloc(ptr,bytes); + return zrealloc_usable(ptr,bytes,NULL); } /* Use like free() for memory obtained by RedisModule_Alloc() and @@ -9951,6 +9958,9 @@ /* Similar to RM_MallocSize, the difference is that RM_MallocUsableSize * returns the usable size of memory by the module. */ size_t RM_MallocUsableSize(void *ptr) { + /* It is safe to use 'zmalloc_usable_size()' to manipulate additional + * memory space, as we guarantee that the compiler can recognize this + * after 'RM_Alloc', 'RM_TryAlloc', 'RM_Realloc', or 'RM_Calloc'. */ return zmalloc_usable_size(ptr); } diff -Nru redis-7.0.10/src/networking.c redis-7.0.11/src/networking.c --- redis-7.0.10/src/networking.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/networking.c 2023-04-17 05:54:03.000000000 -0700 @@ -131,7 +131,7 @@ connSetReadHandler(conn, readQueryFromClient); connSetPrivateData(conn, c); } - c->buf = zmalloc(PROTO_REPLY_CHUNK_BYTES); + c->buf = zmalloc_usable(PROTO_REPLY_CHUNK_BYTES, &c->buf_usable_size); selectDb(c,0); uint64_t client_id; atomicGetIncr(server.next_client_id, client_id, 1); @@ -140,7 +140,6 @@ c->conn = conn; c->name = NULL; c->bufpos = 0; - c->buf_usable_size = zmalloc_usable_size(c->buf); c->buf_peak = c->buf_usable_size; c->buf_peak_last_reset_time = server.unixtime; c->ref_repl_buf_node = NULL; @@ -690,11 +689,12 @@ if (tail->size - tail->used > tail->size / 4 && tail->used < PROTO_REPLY_CHUNK_BYTES) { + size_t usable_size; size_t old_size = tail->size; - tail = zrealloc(tail, tail->used + sizeof(clientReplyBlock)); + tail = zrealloc_usable(tail, tail->used + sizeof(clientReplyBlock), &usable_size); /* take over the allocation's internal fragmentation (at least for * memory usage tracking) */ - tail->size = zmalloc_usable_size(tail) - sizeof(clientReplyBlock); + tail->size = usable_size - sizeof(clientReplyBlock); c->reply_bytes = c->reply_bytes + tail->size - old_size; listNodeValue(ln) = tail; } @@ -770,9 +770,10 @@ listDelNode(c->reply,ln); } else { /* Create a new node */ - clientReplyBlock *buf = zmalloc(length + sizeof(clientReplyBlock)); + size_t usable_size; + clientReplyBlock *buf = zmalloc_usable(length + sizeof(clientReplyBlock), &usable_size); /* Take over the allocation's internal fragmentation */ - buf->size = zmalloc_usable_size(buf) - sizeof(clientReplyBlock); + buf->size = usable_size - sizeof(clientReplyBlock); buf->used = length; memcpy(buf->buf, s, length); listNodeValue(ln) = buf; diff -Nru redis-7.0.10/src/server.c redis-7.0.11/src/server.c --- redis-7.0.10/src/server.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/server.c 2023-04-17 05:54:03.000000000 -0700 @@ -1942,6 +1942,7 @@ server.aof_selected_db = -1; /* Make sure the first time will not match */ server.aof_flush_postponed_start = 0; server.aof_last_incr_size = 0; + server.aof_last_incr_fsync_offset = 0; server.active_defrag_running = 0; server.notify_keyspace_events = 0; server.blocked_clients = 0; diff -Nru redis-7.0.10/src/server.h redis-7.0.11/src/server.h --- redis-7.0.10/src/server.h 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/server.h 2023-04-17 05:54:03.000000000 -0700 @@ -1666,7 +1666,8 @@ off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */ off_t aof_current_size; /* AOF current size (Including BASE + INCRs). */ off_t aof_last_incr_size; /* The size of the latest incr AOF. */ - off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */ + off_t aof_last_incr_fsync_offset; /* AOF offset which is already requested to be synced to disk. + * Compare with the aof_last_incr_size. */ int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */ int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */ sds aof_buf; /* AOF buffer, written before entering the event loop */ diff -Nru redis-7.0.10/src/t_hash.c redis-7.0.11/src/t_hash.c --- redis-7.0.10/src/t_hash.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/t_hash.c 2023-04-17 05:54:03.000000000 -0700 @@ -670,6 +670,10 @@ unsigned int vlen; if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; + if (isnan(incr) || isinf(incr)) { + addReplyError(c,"value is NaN or Infinity"); + return; + } if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) { if (vstr) { diff -Nru redis-7.0.10/src/version.h redis-7.0.11/src/version.h --- redis-7.0.10/src/version.h 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/version.h 2023-04-17 05:54:03.000000000 -0700 @@ -1,2 +1,2 @@ -#define REDIS_VERSION "7.0.10" -#define REDIS_VERSION_NUM 0x0007000a +#define REDIS_VERSION "7.0.11" +#define REDIS_VERSION_NUM 0x0007000b diff -Nru redis-7.0.10/src/zmalloc.c redis-7.0.11/src/zmalloc.c --- redis-7.0.10/src/zmalloc.c 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/zmalloc.c 2023-04-17 05:54:03.000000000 -0700 @@ -59,14 +59,12 @@ #ifdef HAVE_MALLOC_SIZE #define PREFIX_SIZE (0) -#define ASSERT_NO_SIZE_OVERFLOW(sz) #else #if defined(__sun) || defined(__sparc) || defined(__sparc__) #define PREFIX_SIZE (sizeof(long long)) #else #define PREFIX_SIZE (sizeof(size_t)) #endif -#define ASSERT_NO_SIZE_OVERFLOW(sz) assert((sz) + PREFIX_SIZE > (sz)) #endif /* When using the libc allocator, use a minimum allocation size to match the @@ -103,10 +101,18 @@ static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; +#ifdef HAVE_MALLOC_SIZE +void *extend_to_usable(void *ptr, size_t size) { + UNUSED(size); + return ptr; +} +#endif + /* Try allocating memory, and return NULL if failed. * '*usable' is set to the usable size if non NULL. */ -void *ztrymalloc_usable(size_t size, size_t *usable) { - ASSERT_NO_SIZE_OVERFLOW(size); +static inline void *ztrymalloc_usable_internal(size_t size, size_t *usable) { + /* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */ + if (size >= SIZE_MAX/2) return NULL; void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE); if (!ptr) return NULL; @@ -123,24 +129,39 @@ #endif } +void *ztrymalloc_usable(size_t size, size_t *usable) { + size_t usable_size = 0; + void *ptr = ztrymalloc_usable_internal(size, &usable_size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; + return ptr; +} + /* Allocate memory or panic */ void *zmalloc(size_t size) { - void *ptr = ztrymalloc_usable(size, NULL); + void *ptr = ztrymalloc_usable_internal(size, NULL); if (!ptr) zmalloc_oom_handler(size); return ptr; } /* Try allocating memory, and return NULL if failed. */ void *ztrymalloc(size_t size) { - void *ptr = ztrymalloc_usable(size, NULL); + void *ptr = ztrymalloc_usable_internal(size, NULL); return ptr; } /* Allocate memory or panic. * '*usable' is set to the usable size if non NULL. */ void *zmalloc_usable(size_t size, size_t *usable) { - void *ptr = ztrymalloc_usable(size, usable); + size_t usable_size = 0; + void *ptr = ztrymalloc_usable_internal(size, &usable_size); if (!ptr) zmalloc_oom_handler(size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; return ptr; } @@ -149,7 +170,7 @@ * Currently implemented only for jemalloc. Used for online defragmentation. */ #ifdef HAVE_DEFRAG void *zmalloc_no_tcache(size_t size) { - ASSERT_NO_SIZE_OVERFLOW(size); + if (size >= SIZE_MAX/2) zmalloc_oom_handler(size); void *ptr = mallocx(size+PREFIX_SIZE, MALLOCX_TCACHE_NONE); if (!ptr) zmalloc_oom_handler(size); update_zmalloc_stat_alloc(zmalloc_size(ptr)); @@ -165,8 +186,9 @@ /* Try allocating memory and zero it, and return NULL if failed. * '*usable' is set to the usable size if non NULL. */ -void *ztrycalloc_usable(size_t size, size_t *usable) { - ASSERT_NO_SIZE_OVERFLOW(size); +static inline void *ztrycalloc_usable_internal(size_t size, size_t *usable) { + /* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */ + if (size >= SIZE_MAX/2) return NULL; void *ptr = calloc(1, MALLOC_MIN_SIZE(size)+PREFIX_SIZE); if (ptr == NULL) return NULL; @@ -183,6 +205,16 @@ #endif } +void *ztrycalloc_usable(size_t size, size_t *usable) { + size_t usable_size = 0; + void *ptr = ztrycalloc_usable_internal(size, &usable_size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; + return ptr; +} + /* Allocate memory and zero it or panic. * We need this wrapper to have a calloc compatible signature */ void *zcalloc_num(size_t num, size_t size) { @@ -192,36 +224,40 @@ zmalloc_oom_handler(SIZE_MAX); return NULL; } - void *ptr = ztrycalloc_usable(num*size, NULL); + void *ptr = ztrycalloc_usable_internal(num*size, NULL); if (!ptr) zmalloc_oom_handler(num*size); return ptr; } /* Allocate memory and zero it or panic */ void *zcalloc(size_t size) { - void *ptr = ztrycalloc_usable(size, NULL); + void *ptr = ztrycalloc_usable_internal(size, NULL); if (!ptr) zmalloc_oom_handler(size); return ptr; } /* Try allocating memory, and return NULL if failed. */ void *ztrycalloc(size_t size) { - void *ptr = ztrycalloc_usable(size, NULL); + void *ptr = ztrycalloc_usable_internal(size, NULL); return ptr; } /* Allocate memory or panic. * '*usable' is set to the usable size if non NULL. */ void *zcalloc_usable(size_t size, size_t *usable) { - void *ptr = ztrycalloc_usable(size, usable); + size_t usable_size = 0; + void *ptr = ztrycalloc_usable_internal(size, &usable_size); if (!ptr) zmalloc_oom_handler(size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; return ptr; } /* Try reallocating memory, and return NULL if failed. * '*usable' is set to the usable size if non NULL. */ -void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable) { - ASSERT_NO_SIZE_OVERFLOW(size); +static inline void *ztryrealloc_usable_internal(void *ptr, size_t size, size_t *usable) { #ifndef HAVE_MALLOC_SIZE void *realptr; #endif @@ -238,6 +274,13 @@ if (ptr == NULL) return ztrymalloc_usable(size, usable); + /* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */ + if (size >= SIZE_MAX/2) { + zfree(ptr); + if (usable) *usable = 0; + return NULL; + } + #ifdef HAVE_MALLOC_SIZE oldsize = zmalloc_size(ptr); newptr = realloc(ptr,size); @@ -268,24 +311,39 @@ #endif } +void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable) { + size_t usable_size = 0; + ptr = ztryrealloc_usable_internal(ptr, size, &usable_size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; + return ptr; +} + /* Reallocate memory and zero it or panic */ void *zrealloc(void *ptr, size_t size) { - ptr = ztryrealloc_usable(ptr, size, NULL); + ptr = ztryrealloc_usable_internal(ptr, size, NULL); if (!ptr && size != 0) zmalloc_oom_handler(size); return ptr; } /* Try Reallocating memory, and return NULL if failed. */ void *ztryrealloc(void *ptr, size_t size) { - ptr = ztryrealloc_usable(ptr, size, NULL); + ptr = ztryrealloc_usable_internal(ptr, size, NULL); return ptr; } /* Reallocate memory or panic. * '*usable' is set to the usable size if non NULL. */ void *zrealloc_usable(void *ptr, size_t size, size_t *usable) { - ptr = ztryrealloc_usable(ptr, size, usable); + size_t usable_size = 0; + ptr = ztryrealloc_usable(ptr, size, &usable_size); if (!ptr && size != 0) zmalloc_oom_handler(size); +#ifdef HAVE_MALLOC_SIZE + ptr = extend_to_usable(ptr, usable_size); +#endif + if (usable) *usable = usable_size; return ptr; } diff -Nru redis-7.0.10/src/zmalloc.h redis-7.0.11/src/zmalloc.h --- redis-7.0.10/src/zmalloc.h 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/src/zmalloc.h 2023-04-17 05:54:03.000000000 -0700 @@ -133,7 +133,22 @@ size_t zmalloc_size(void *ptr); size_t zmalloc_usable_size(void *ptr); #else +/* If we use 'zmalloc_usable_size()' to obtain additional available memory size + * and manipulate it, we need to call 'extend_to_usable()' afterwards to ensure + * the compiler recognizes this extra memory. However, if we use the pointer + * obtained from z[*]_usable() family functions, there is no need for this step. */ #define zmalloc_usable_size(p) zmalloc_size(p) + +/* derived from https://github.com/systemd/systemd/pull/25688 + * We use zmalloc_usable_size() everywhere to use memory blocks, but that is an abuse since the + * malloc_usable_size() isn't meant for this kind of use, it is for diagnostics only. That is also why the + * behavior is flaky when built with _FORTIFY_SOURCE, the compiler can sense that we reach outside + * the allocated block and SIGABRT. + * We use a dummy allocator function to tell the compiler that the new size of ptr is newsize. + * The implementation returns the pointer as is; the only reason for its existence is as a conduit for the + * alloc_size attribute. This cannot be a static inline because gcc then loses the attributes on the function. + * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */ +__attribute__((alloc_size(2),noinline)) void *extend_to_usable(void *ptr, size_t size); #endif int get_proc_stat_ll(int i, long long *res); diff -Nru redis-7.0.10/tests/integration/corrupt-dump.tcl redis-7.0.11/tests/integration/corrupt-dump.tcl --- redis-7.0.10/tests/integration/corrupt-dump.tcl 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/tests/integration/corrupt-dump.tcl 2023-04-17 05:54:03.000000000 -0700 @@ -790,5 +790,16 @@ } } +test {corrupt payload: fuzzer findings - valgrind fishy value warning} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { + r config set sanitize-dump-payload yes + r debug set-skip-checksum-validation 1 + catch {r restore _key 0 "\x13\x01\x10\x00\x00\x01\x81\xCC\x07\xDC\xF2\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x05\x01\x03\x01\x2C\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x05\x01\x02\x01\x3C\x01\x00\x01\x01\x01\x02\x01\x05\x01\xFF\x02\xD0\x00\x00\x01\x81\xCC\x07\xDD\x2E\x00\x81\x00\x00\x01\x81\xCC\x07\xDC\xF2\x00\x81\x00\x00\x01\x81\xCC\x07\xDD\x1E\x00\x03\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x81\xCC\x07\xDD\x1E\x00\x02\x01\x00\x00\x01\x81\xCC\x07\xDD\x1E\x00\x00\x00\x00\x00\x00\x00\x00\x71\xDD\x07\xCC\x81\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\x58\xDD\x07\xCC\x81\x01\x00\x00\x01\x00\x00\x01\x81\xCC\x07\xDD\x1E\x00\x00\x00\x00\x00\x00\x00\x00\x0A\x00\x2F\xB0\xD1\x15\x0A\x97\x87\x6B"} err + assert_match "*Bad data format*" $err + r ping + } +} + + } ;# tags diff -Nru redis-7.0.10/tests/unit/acl.tcl redis-7.0.11/tests/unit/acl.tcl --- redis-7.0.10/tests/unit/acl.tcl 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/tests/unit/acl.tcl 2023-04-17 05:54:03.000000000 -0700 @@ -285,6 +285,20 @@ $rd close } {0} + test {Subscribers are killed when revoked of allchannels permission} { + set rd [redis_deferring_client] + r ACL setuser psuser allchannels + $rd AUTH psuser pspass + $rd read + $rd CLIENT SETNAME deathrow + $rd read + $rd PSUBSCRIBE foo + $rd read + r ACL setuser psuser resetchannels + assert_no_match {*deathrow*} [r CLIENT LIST] + $rd close + } {0} + test {Subscribers are pardoned if literal permissions are retained and/or gaining allchannels} { set rd [redis_deferring_client] r ACL setuser psuser resetchannels &foo:1 &bar:* &orders diff -Nru redis-7.0.10/tests/unit/type/hash.tcl redis-7.0.11/tests/unit/type/hash.tcl --- redis-7.0.10/tests/unit/type/hash.tcl 2023-03-20 10:16:50.000000000 -0700 +++ redis-7.0.11/tests/unit/type/hash.tcl 2023-04-17 05:54:03.000000000 -0700 @@ -826,4 +826,11 @@ set _ $k } {ZIP_INT_8B 127 ZIP_INT_16B 32767 ZIP_INT_32B 2147483647 ZIP_INT_64B 9223372036854775808 ZIP_INT_IMM_MIN 0 ZIP_INT_IMM_MAX 12} + # On some platforms strtold("+inf") with valgrind returns a non-inf result + if {!$::valgrind} { + test {HINCRBYFLOAT does not allow NaN or Infinity} { + assert_error "*value is NaN or Infinity*" {r hincrbyfloat hfoo field +inf} + assert_equal 0 [r exists hfoo] + } + } }