Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package redis for openSUSE:Factory checked in at 2026-05-06 19:17:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/redis (Old) and /work/SRC/openSUSE:Factory/.redis.new.30200 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "redis" Wed May 6 19:17:49 2026 rev:14 rq:1350996 version:8.6.3 Changes: -------- --- /work/SRC/openSUSE:Factory/redis/redis.changes 2026-03-27 06:49:42.974487766 +0100 +++ /work/SRC/openSUSE:Factory/.redis.new.30200/redis.changes 2026-05-06 19:18:34.686768610 +0200 @@ -1,0 +2,88 @@ +Tue May 5 14:08:33 UTC 2026 - Marcus Rueckert <[email protected]> + +- Updated to 8.6.3 + - Security fixes + - (CVE-2026-23479) Use-After-Free in unblock client flow may + lead to Remote Code Execution. + - (CVE-2026-25243) Invalid memory access in RESTORE may lead to + Remote Code Execution + - (CVE-2026-23631) Lua Use-After-Free may lead to remote code + execution + - (CVE-2026-25588) Invalid memory access in RESTORE may lead to + Remote Code Execution (Time Series) + - (CVE-2026-25589) Invalid memory access in RESTORE may lead to + Remote Code Execution (Probabilistic) + - Bug fixes + - SUBSCRIBE, PSUBSCRIBE, SSUBSCRIBE: crash on OOM (RED-167788) + - CONFIG SET: some settings allow invalid characters + (RED-167787) + - SCRIPT DEBUG: potential crash on scripts (RED-175507) + - VADD: crash or buffer overflow on large REDUCE value + (RED-170921) + - VSET: crash on huge allocations (MOD-12678) + - Potential crash on disconnections and TLS failures (Time + Series) (MOD-14850) + - RediSearch/RediSearch#8745 Crash when many keys receive + expirations under heavy TTL activity (MOD-14500) + - RediSearch/RediSearch#8848 HNSW vector index memory growth + under high-churn workloads until shard restart (MOD-13761) + - RediSearch/RediSearch#8205, RediSearch/RediSearch#8259 + FT.HYBRID VSIM RANGE + FILTER incorrectly returns zero + results (MOD-12370, MOD-13884) + - RediSearch/RediSearch#9182 FT.PROFILE HYBRID returns an empty + reply (MOD-14778) + - RediSearch/RediSearch#8129, RediSearch/RediSearch#8140 + FT.PROFILE reports an incorrect shard total profile time + (MOD-13735, MOD-13181) + - RediSearch/RediSearch#9047 FT.PROFILE output is inconsistent + when a profiled value is missing (MOD-10560) + - RediSearch/RediSearch#8791 FT.EXPLAIN does not lock, causing + a race with concurrent index changes (MOD-14461) + - RediSearch/RediSearch#8382 Crash when indexing negative zero + (-0.0) (MOD-13904) + - RediSearch/RediSearch#8590 FILTER returns inconsistent + results with multiple indexes sharing field aliases + (MOD-14063) + - RediSearch/RediSearch#8660 FILTER behavior depends on + property order in the expression (MOD-14065) + - RediSearch/RediSearch#8593 Filter expressions are evaluated + for indexes that do not match the document type (MOD-14064) + - RediSearch/RediSearch#8591 Documents are inconsistently + included or excluded depending on the indexing path taken + (MOD-13948) + - RediSearch/RediSearch#8589 RENAME notification handler loads + the wrong key, causing stale index entries after a rename + (MOD-14328) + - RediSearch/RediSearch#9012 PERSIST and HPERSIST notifications + are not reflected in index expiration tracking (MOD-14800) + - RediSearch/RediSearch#9079 FT.SPELLCHECK treats PARAMS + placeholders as literal terms instead of resolving them + (MOD-10596) + - RediSearch/RediSearch#8462 GC out-of-memory on replica shards + leaves the replica in an inconsistent state (MOD-14066) + - RediSearch/RediSearch#9066 Race condition in FT.HYBRID causes + intermittent failures under concurrent hybrid query load + (MOD-14732) + - RediSearch/RediSearch#8109, RediSearch/RediSearch#8149 + Configuration registration omits module parameters, causing + them to be unexposed or misapplied (RED-171841) + - RediSearch/RediSearch#9163 Crash on FT.SEARCH when topology + validation fails (for example, some nodes unreachable) + (MOD-14475) + - RediSearch/RediSearch#8395 FT.SEARCH fails with "Query + requires unavailable slots" after shard restart or failover + (MOD-13828) + - RediSearch/RediSearch#8451 FT.INFO-style output no longer + reports zero-index summary data when no indices exist + (MOD-14079) + - RediSearch/RediSearch#9078 FT.CREATE now rejects schema + definitions with invalid option combinations at creation time + (MOD-14655) + - RediSearch/RediSearch#8051, RediSearch/RediSearch#8114 Crash + diagnostics now include the IndexSpec of the index the + failing thread was working on (MOD-7574) + - Metrics + - RediSearch/RediSearch#8210, RediSearch/RediSearch#8231 + FT.PROFILE: added queue time tracking (MOD-13602) + +------------------------------------------------------------------- Old: ---- redis-8.6.2.tar.gz New: ---- redis-8.6.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ redis.spec ++++++ --- /var/tmp/diff_new_pack.7Q0dHy/_old 2026-05-06 19:18:35.570805040 +0200 +++ /var/tmp/diff_new_pack.7Q0dHy/_new 2026-05-06 19:18:35.582805534 +0200 @@ -20,7 +20,7 @@ %define _log_dir %{_localstatedir}/log/%{name} %define _conf_dir %{_sysconfdir}/%{name} Name: redis -Version: 8.6.2 +Version: 8.6.3 Release: 0 Summary: Persistent key-value database License: AGPL-3.0-only ++++++ redis-8.6.2.tar.gz -> redis-8.6.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/00-RELEASENOTES new/redis-8.6.3/00-RELEASENOTES --- old/redis-8.6.2/00-RELEASENOTES 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/00-RELEASENOTES 2026-05-05 15:23:21.000000000 +0200 @@ -21,6 +21,57 @@ ================================================================================ +Redis 8.6.3 Released Tue 5 May 2026 16:00:00 IST +================================================================================ + +Update urgency: `SECURITY`: There are security fixes in the release. + +### Security fixes + +- (CVE-2026-23479) Use-After-Free in unblock client flow may lead to Remote Code Execution. +- (CVE-2026-25243) Invalid memory access in `RESTORE` may lead to Remote Code Execution +- (CVE-2026-23631) Lua Use-After-Free may lead to remote code execution +- (CVE-2026-25588) Invalid memory access in `RESTORE` may lead to Remote Code Execution (Time Series) +- (CVE-2026-25589) Invalid memory access in `RESTORE` may lead to Remote Code Execution (Probabilistic) + +### Bug fixes + +- `SUBSCRIBE`, `PSUBSCRIBE`, `SSUBSCRIBE`: crash on OOM (RED-167788) +- `CONFIG SET`: some settings allow invalid characters (RED-167787) +- `SCRIPT DEBUG`: potential crash on scripts (RED-175507) +- `VADD`: crash or buffer overflow on large `REDUCE` value (RED-170921) +- `VSET`: crash on huge allocations (MOD-12678) +- Potential crash on disconnections and TLS failures (Time Series) (MOD-14850) +- #Q8745 Crash when many keys receive expirations under heavy TTL activity (MOD-14500) +- #Q8848 HNSW vector index memory growth under high-churn workloads until shard restart (MOD-13761) +- #Q8205, #Q8259 `FT.HYBRID` `VSIM RANGE` + `FILTER` incorrectly returns zero results (MOD-12370, MOD-13884) +- #Q9182 `FT.PROFILE HYBRID` returns an empty reply (MOD-14778) +- #Q8129, #Q8140 `FT.PROFILE` reports an incorrect shard total profile time (MOD-13735, MOD-13181) +- #Q9047 `FT.PROFILE` output is inconsistent when a profiled value is missing (MOD-10560) +- #Q8791 `FT.EXPLAIN` does not lock, causing a race with concurrent index changes (MOD-14461) +- #Q8382 Crash when indexing negative zero (-0.0) (MOD-13904) +- #Q8590 `FILTER` returns inconsistent results with multiple indexes sharing field aliases (MOD-14063) +- #Q8660 `FILTER` behavior depends on property order in the expression (MOD-14065) +- #Q8593 Filter expressions are evaluated for indexes that do not match the document type (MOD-14064) +- #Q8591 Documents are inconsistently included or excluded depending on the indexing path taken (MOD-13948) +- #Q8589 `RENAME` notification handler loads the wrong key, causing stale index entries after a rename (MOD-14328) +- #Q9012 `PERSIST` and `HPERSIST` notifications are not reflected in index expiration tracking (MOD-14800) +- #Q9079 `FT.SPELLCHECK` treats `PARAMS` placeholders as literal terms instead of resolving them (MOD-10596) +- #Q8462 GC out-of-memory on replica shards leaves the replica in an inconsistent state (MOD-14066) +- #Q9066 Race condition in `FT.HYBRID` causes intermittent failures under concurrent hybrid query load (MOD-14732) +- #Q8109, #Q8149 Configuration registration omits module parameters, causing them to be unexposed or misapplied (RED-171841) +- #Q9163 Crash on `FT.SEARCH` when topology validation fails (for example, some nodes unreachable) (MOD-14475) +- #Q8395 `FT.SEARCH` fails with "Query requires unavailable slots" after shard restart or failover (MOD-13828) +- #Q8451 `FT.INFO`-style output no longer reports zero-index summary data when no indices exist (MOD-14079) +- #Q9078 `FT.CREATE` now rejects schema definitions with invalid option combinations at creation time (MOD-14655) +- #Q8051, #Q8114 Crash diagnostics now include the `IndexSpec` of the index the failing thread was working on (MOD-7574) + +### Metrics + +- #Q8210, #Q8231 `FT.PROFILE`: added queue time tracking (MOD-13602) + + +================================================================================ Redis 8.6.2 Released Tue 24 Mar 2026 12:00:00 IST ================================================================================ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/redisbloom/Makefile new/redis-8.6.3/modules/redisbloom/Makefile --- old/redis-8.6.2/modules/redisbloom/Makefile 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/modules/redisbloom/Makefile 2026-05-05 15:23:21.000000000 +0200 @@ -1,5 +1,5 @@ SRC_DIR = src -MODULE_VERSION = v8.6.0 +MODULE_VERSION = v8.6.2 MODULE_REPO = https://github.com/redisbloom/redisbloom TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/redisearch/Makefile new/redis-8.6.3/modules/redisearch/Makefile --- old/redis-8.6.2/modules/redisearch/Makefile 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/modules/redisearch/Makefile 2026-05-05 15:23:21.000000000 +0200 @@ -1,5 +1,5 @@ SRC_DIR = src -MODULE_VERSION = v8.6.0 +MODULE_VERSION = v8.6.7 MODULE_REPO = https://github.com/redisearch/redisearch TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/redistimeseries/Makefile new/redis-8.6.3/modules/redistimeseries/Makefile --- old/redis-8.6.2/modules/redistimeseries/Makefile 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/modules/redistimeseries/Makefile 2026-05-05 15:23:21.000000000 +0200 @@ -1,5 +1,5 @@ SRC_DIR = src -MODULE_VERSION = v8.6.0 +MODULE_VERSION = v8.6.2 MODULE_REPO = https://github.com/redistimeseries/redistimeseries TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/vector-sets/tests/dimension_max_limit.py new/redis-8.6.3/modules/vector-sets/tests/dimension_max_limit.py --- old/redis-8.6.2/modules/vector-sets/tests/dimension_max_limit.py 1970-01-01 01:00:00.000000000 +0100 +++ new/redis-8.6.3/modules/vector-sets/tests/dimension_max_limit.py 2026-05-05 15:23:21.000000000 +0200 @@ -0,0 +1,129 @@ +from test import TestCase, generate_random_vector +import struct +import redis.exceptions + +MAX_DIM = 65536 + + +class DimensionMaxLimitVaddAtLimit(TestCase): + def getname(self): + return "[regression] VADD VALUES dim == MAX_DIM accepted" + + def estimated_runtime(self): + return 0.5 + + def test(self): + dim = MAX_DIM + vec = generate_random_vector(dim) + + result = self.redis.execute_command( + 'VADD', self.test_key, + 'VALUES', dim, + *[str(x) for x in vec], + f"{self.test_key}:item:maxdim") + assert result == 1, "VADD with dimension at the limit should succeed" + + +class DimensionMaxLimitVaddAboveLimit(TestCase): + def getname(self): + return "[regression] VADD VALUES dim > MAX_DIM rejected" + + def estimated_runtime(self): + return 0.1 + + def test(self): + too_big_dim = MAX_DIM + 1 + too_big_vec = generate_random_vector(16) + try: + self.redis.execute_command( + 'VADD', self.test_key, + 'VALUES', too_big_dim, + *[str(x) for x in too_big_vec], + f"{self.test_key}:item:toolarge") + assert False, "VADD with dimension above the limit should fail" + except redis.exceptions.ResponseError as e: + # parseVector returns NULL so caller uses the generic invalid spec error + assert "invalid vector specification" in str(e), ( + f"Expected invalid vector specification error, got: {e}") + + +class DimensionMaxLimitVsimAtLimit(TestCase): + def getname(self): + return "[regression] VSIM VALUES dim == MAX_DIM accepted" + + def estimated_runtime(self): + return 0.5 + + def test(self): + # Insert a vector at the maximum allowed dimension, then query at the same dimension. + dim = MAX_DIM + base_vec = generate_random_vector(dim) + + result = self.redis.execute_command( + 'VADD', self.test_key, + 'VALUES', dim, + *[str(x) for x in base_vec], + f"{self.test_key}:item:1") + assert result == 1, "VADD with dimension at the limit should succeed" + + query = generate_random_vector(dim) + res = self.redis.execute_command( + 'VSIM', self.test_key, + 'VALUES', dim, + *[str(x) for x in query], + 'COUNT', 1) + assert isinstance(res, list), "VSIM with dimension at the limit should return a list" + + +class DimensionMaxLimitVsimAboveLimit(TestCase): + def getname(self): + return "[regression] VSIM VALUES dim > MAX_DIM rejected" + + def estimated_runtime(self): + return 0.1 + + def test(self): + # Create a small index, then issue a VSIM with an over-limit dimension. + base_dim = 16 + base_vec = generate_random_vector(base_dim) + result = self.redis.execute_command( + 'VADD', self.test_key, + 'VALUES', base_dim, + *[str(x) for x in base_vec], + f"{self.test_key}:item:1") + assert result == 1, "VADD with base_dim should succeed" + + too_big_dim = MAX_DIM + 1 + too_big_vec = generate_random_vector(16) + try: + self.redis.execute_command( + 'VSIM', self.test_key, + 'VALUES', too_big_dim, + *[str(x) for x in too_big_vec], + 'COUNT', 1) + assert False, "VSIM with dimension above the limit should fail" + except redis.exceptions.ResponseError as e: + assert "invalid vector specification" in str(e), ( + f"Expected invalid vector specification error in VSIM, got: {e}") + + +class DimensionMaxLimitHugeDimension(TestCase): + def getname(self): + return "[regression] VADD VALUES absurdly large dim rejected" + + def estimated_runtime(self): + return 0.1 + + def test(self): + # Extremely large dimension close to LLONG_MAX should also be rejected safely. + huge_dim = 9223372036854775807 # LLONG_MAX from the original report + try: + self.redis.execute_command( + 'VADD', self.test_key, + 'VALUES', huge_dim, + '0') # Just a dummy value; parseVector should reject based on dimension alone + assert False, "VADD with absurdly large dimension should fail" + except redis.exceptions.ResponseError as e: + assert "invalid vector specification" in str(e), ( + f"Expected invalid vector specification error for huge dim, got: {e}") + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/vector-sets/tests/dimension_validation.py new/redis-8.6.3/modules/vector-sets/tests/dimension_validation.py --- old/redis-8.6.2/modules/vector-sets/tests/dimension_validation.py 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/modules/vector-sets/tests/dimension_validation.py 2026-05-05 15:23:21.000000000 +0200 @@ -65,3 +65,33 @@ assert False, "VSIM with wrong dimension should fail" except redis.exceptions.ResponseError as e: assert "Input dimension mismatch for projection" in str(e), f"Expected dimension mismatch error in VSIM, got: {e}" + +class ReduceDimConstraintValidation(TestCase): + def getname(self): + return "[regression] VADD enforces reduce_dim <= dim" + + def estimated_runtime(self): + return 0.1 + + def test(self): + import struct + + dim = 16 + reduce_dim = dim + 1 # Intentionally larger than dim + + # Build a simple FP32 vector of the given dimension. + vec = [0.0] * dim + vec_bytes = struct.pack(f'{dim}f', *vec) + + try: + self.redis.execute_command( + 'VADD', self.test_key, + 'REDUCE', reduce_dim, + 'FP32', vec_bytes, + f'{self.test_key}:item:reducemismatch') + assert False, "VADD with reduce_dim > dim should fail" + except redis.exceptions.ResponseError as e: + # Same generic validation error path as other vector spec problems. + assert "invalid vector specification" in str(e), ( + f"Expected invalid vector error, got: {e}") + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/modules/vector-sets/vset.c new/redis-8.6.3/modules/vector-sets/vset.c --- old/redis-8.6.2/modules/vector-sets/vset.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/modules/vector-sets/vset.c 2026-05-05 15:23:21.000000000 +0200 @@ -134,6 +134,9 @@ // Default num elements returned by VSIM. #define VSET_DEFAULT_COUNT 10 +// Maximum allowed vector dimension for input vectors and sets. +#define VSET_MAX_VECTOR_DIM (1<<16) + /* ========================== Internal data structure ====================== */ /* Our abstract data type needs a dual representation similar to Redis @@ -408,6 +411,7 @@ // Must be 4 bytes per component. if (vec_raw_len % 4 || vec_raw_len < 4) return NULL; *dim = vec_raw_len/4; + if (*dim > VSET_MAX_VECTOR_DIM) return NULL; vec = RedisModule_Alloc(vec_raw_len); if (!vec) return NULL; @@ -417,7 +421,7 @@ if (argc < start_idx + 2) return NULL; // Need at least the dimension. long long vdim; // Vector dimension passed by the user. if (RedisModule_StringToLongLong(argv[start_idx+1],&vdim) - != REDISMODULE_OK || vdim < 1) return NULL; + != REDISMODULE_OK || vdim < 1 || vdim > VSET_MAX_VECTOR_DIM) return NULL; // Check that all the arguments are available. if (argc < start_idx + 2 + vdim) return NULL; @@ -441,6 +445,12 @@ return NULL; // Unknown format. } + // reduce_dim must be <= dim + if (reduce_dim && *reduce_dim && *reduce_dim > *dim) { + if (vec) RedisModule_Free(vec); + return NULL; + } + if (consumed_args) *consumed_args = consumed; return vec; } @@ -1960,6 +1970,15 @@ uint32_t quant_type = hnsw_config & 0xff; uint32_t hnsw_m = (hnsw_config >> 8) & 0xffff; + /* Validate dimension loaded from RDB to enforce invariants and + * avoid absurd allocations or inconsistent state. */ + if (dim == 0 || dim > VSET_MAX_VECTOR_DIM) { + RedisModule_LogIOError(rdb, "warning", + "Invalid vector dimension in RDB: dim=%u (max allowed %u)", + (unsigned)dim, (unsigned)VSET_MAX_VECTOR_DIM); + return NULL; + } + /* Check that the quantization type is correct. Otherwise * return ASAP signaling the error. */ if (quant_type != HNSW_QUANT_NONE && @@ -1981,14 +2000,44 @@ uint32_t input_dim = RedisModule_LoadUnsigned(rdb); if (RedisModule_IsIOError(rdb)) goto ioerr; uint32_t output_dim = dim; - size_t matrix_size = sizeof(float) * input_dim * output_dim; - vset->proj_matrix = RedisModule_Alloc(matrix_size); - vset->proj_input_size = input_dim; + /* Sanity check projection dimensions. */ + if (input_dim == 0 || output_dim == 0 || input_dim > VSET_MAX_VECTOR_DIM || output_dim > input_dim) { + RedisModule_LogIOError(rdb, "warning", + "Invalid projection matrix dimensions: input_dim=%u, output_dim=%u (max allowed %u)", + (unsigned)input_dim, (unsigned)output_dim, + (unsigned)VSET_MAX_VECTOR_DIM); + goto ioerr; + } + + /* Check for overflow in matrix_size = sizeof(float) * input_dim * output_dim. */ + #if SIZE_MAX == UINT32_MAX + uint64_t product = (uint64_t) output_dim * (uint64_t) input_dim * sizeof(float); + if (product > SIZE_MAX) { + RedisModule_LogIOError(rdb, "warning", + "Projection matrix size overflow (output_dim too large): input_dim=%u, output_dim=%u", + (unsigned)input_dim, (unsigned)output_dim); + goto ioerr; + } + #endif - // Load projection matrix as a binary blob - char *matrix_blob = RedisModule_LoadStringBuffer(rdb, NULL); + size_t matrix_size = sizeof(float) * (size_t)input_dim * (size_t)output_dim; + + /* Load projection matrix as a binary blob and validate length. */ + size_t blob_len = 0; + char *matrix_blob = RedisModule_LoadStringBuffer(rdb, &blob_len); if (matrix_blob == NULL) goto ioerr; + + if (blob_len != matrix_size) { + RedisModule_LogIOError(rdb, "warning", + "Mismatching projection matrix length: expected=%zu, got=%zu", + matrix_size, blob_len); + RedisModule_Free(matrix_blob); + goto ioerr; + } + + vset->proj_matrix = RedisModule_Alloc(matrix_size); + vset->proj_input_size = input_dim; memcpy(vset->proj_matrix, matrix_blob, matrix_size); RedisModule_Free(matrix_blob); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/blocked.c new/redis-8.6.3/src/blocked.c --- old/redis-8.6.2/src/blocked.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/blocked.c 2026-05-05 15:23:21.000000000 +0200 @@ -691,7 +691,13 @@ client *old_client = server.current_client; server.current_client = c; enterExecutionUnit(1, 0); - processCommandAndResetClient(c); + if (processCommandAndResetClient(c) == C_ERR) { + /* Client was freed during command processing, exit immediately */ + exitExecutionUnit(); + server.current_client = old_client; + return; + } + if (!(c->flags & CLIENT_BLOCKED)) { if (c->flags & CLIENT_MODULE) { moduleCallCommandUnblockedHandler(c); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/cluster.c new/redis-8.6.3/src/cluster.c --- old/redis-8.6.2/src/cluster.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/cluster.c 2026-05-05 15:23:21.000000000 +0200 @@ -805,7 +805,12 @@ } int isValidAuxChar(int c) { - return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL); + /* Reject control characters (0x00-0x1F and 0x7F). */ + if (iscntrl(c)) { + return 0; + } + /* Reject forbidden characters including nodes.conf delimiters and special parsing characters */ + return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~,= \"'\\", c) == NULL); } int isValidAuxString(char *s, unsigned int length) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/commands/psubscribe.json new/redis-8.6.3/src/commands/psubscribe.json --- old/redis-8.6.2/src/commands/psubscribe.json 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/commands/psubscribe.json 2026-05-05 15:23:21.000000000 +0200 @@ -11,7 +11,8 @@ "NOSCRIPT", "LOADING", "STALE", - "SENTINEL" + "SENTINEL", + "DENYOOM" ], "arguments": [ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/commands/ssubscribe.json new/redis-8.6.3/src/commands/ssubscribe.json --- old/redis-8.6.2/src/commands/ssubscribe.json 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/commands/ssubscribe.json 2026-05-05 15:23:21.000000000 +0200 @@ -10,7 +10,8 @@ "PUBSUB", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "DENYOOM" ], "arguments": [ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/commands/subscribe.json new/redis-8.6.3/src/commands/subscribe.json --- old/redis-8.6.2/src/commands/subscribe.json 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/commands/subscribe.json 2026-05-05 15:23:21.000000000 +0200 @@ -12,7 +12,8 @@ "NOSCRIPT", "LOADING", "STALE", - "SENTINEL" + "SENTINEL", + "DENYOOM" ], "arguments": [ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/commands.def new/redis-8.6.3/src/commands.def --- old/redis-8.6.2/src/commands.def 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/commands.def 2026-05-05 15:23:21.000000000 +0200 @@ -11856,13 +11856,13 @@ {MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args}, {MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args}, /* pubsub */ -{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args}, +{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args}, {MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args}, {MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands}, {MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args}, {MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args}, -{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args}, -{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args}, +{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_DENYOOM,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args}, +{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args}, {MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args}, {MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args}, /* scripting */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/config.c new/redis-8.6.3/src/config.c --- old/redis-8.6.2/src/config.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/config.c 2026-05-05 15:23:21.000000000 +0200 @@ -24,6 +24,7 @@ #include <string.h> #include <locale.h> #include <ctype.h> +#include <arpa/inet.h> /*----------------------------------------------------------------------------- * Config file name-value maps. @@ -2452,6 +2453,23 @@ return 1; } +/* Validation function for cluster-announce-ip. + * Ensures the IP address is valid and rejects control characters. */ +static int isValidClusterAnnounceIp(char *val, const char **err) { + unsigned char buf[sizeof(struct in6_addr)]; + /* Empty string is allowed - it will be converted to NULL by EMPTY_STRING_IS_NULL flag */ + if (val[0] == '\0') { + return 1; + } + + if (inet_pton(AF_INET, val, buf) != 1 && + inet_pton(AF_INET6, val, buf) != 1) { + *err = "Cluster announce IP must be a valid IPv4 or IPv6 address"; + return 0; + } + return 1; +} + /* Validate specified string is a valid proc-title-template */ static int isValidProcTitleTemplate(char *val, const char **err) { if (!validateProcTitleTemplate(val)) { @@ -3159,7 +3177,7 @@ createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL), createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL), createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL), - createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp), + createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, isValidClusterAnnounceIp, updateClusterIp), createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL), createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname), createStringConfig("cluster-announce-human-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_human_nodename, NULL, isValidAnnouncedNodename, updateClusterHumanNodename), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/eval.c new/redis-8.6.3/src/eval.c --- old/redis-8.6.2/src/eval.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/eval.c 2026-05-05 15:23:21.000000000 +0200 @@ -1502,7 +1502,9 @@ sdsfree(code); sdsfree(expr); if (lua_pcall(lua,0,1,0)) { - ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1))); + const char *err = lua_tostring(lua,-1); + ldbLog(sdscatfmt(sdsempty(),"<error> %s", + err ? err : "(error object is not a string)")); lua_pop(lua,1); return; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/rdb.c new/redis-8.6.3/src/rdb.c --- old/redis-8.6.2/src/rdb.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/rdb.c 2026-05-05 15:23:21.000000000 +0200 @@ -2834,11 +2834,12 @@ /* search for duplicate records */ sds field = sdstrynewlen(fstr, flen); - if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK || - !lpSafeToAdd(lp, (size_t)flen + vlen)) { + if (!field || !lpSafeToAdd(lp, (size_t)flen + vlen) || + dictAdd(dupSearchDict, field, NULL) != DICT_OK) { rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen); dictRelease(dupSearchDict); sdsfree(field); + lpFree(lp); zfree(encoded); o->ptr = NULL; decrRefCount(o); @@ -3335,7 +3336,6 @@ rdbReportCorruptRDB("Duplicated consumer PEL entry " " loading a stream consumer " "group"); - streamFreeNACK(s, nack); decrRefCount(o); return NULL; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/replication.c new/redis-8.6.3/src/replication.c --- old/redis-8.6.2/src/replication.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/replication.c 2026-05-05 15:23:21.000000000 +0200 @@ -2197,6 +2197,11 @@ /* Asynchronously read the SYNC payload we receive from a master */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ void readSyncBulkPayload(connection *conn) { + /* During full sync, the functions engine is freed right before loading + * the RDB. To avoid this happening while a function is still running, + * delay full sync processing until it finishes. */ + if (isInsideYieldingLongCommand()) return; + char buf[PROTO_IOBUF_LEN]; ssize_t nread, readlen, nwritten; int use_diskless_load = useDisklessLoad(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/sds.c new/redis-8.6.3/src/sds.c --- old/redis-8.6.2/src/sds.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/sds.c 2026-05-05 15:23:21.000000000 +0200 @@ -105,7 +105,14 @@ int hdrlen = sdsHdrSize(type); size_t bufsize; - assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */ + if (trymalloc) { + /* protect against size_t overflow */ + if (initlen + hdrlen + 1 <= initlen) + return NULL; + } else { + assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */ + } + sh = trymalloc? s_trymalloc_usable(hdrlen+initlen+1, &bufsize) : s_malloc_usable(hdrlen+initlen+1, &bufsize); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/version.h new/redis-8.6.3/src/version.h --- old/redis-8.6.2/src/version.h 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/version.h 2026-05-05 15:23:21.000000000 +0200 @@ -1,2 +1,2 @@ -#define REDIS_VERSION "8.6.2" -#define REDIS_VERSION_NUM 0x00080602 +#define REDIS_VERSION "8.6.3" +#define REDIS_VERSION_NUM 0x00080603 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/src/zipmap.c new/redis-8.6.3/src/zipmap.c --- old/redis-8.6.2/src/zipmap.c 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/src/zipmap.c 2026-05-05 15:23:21.000000000 +0200 @@ -387,6 +387,10 @@ /* read the field name length */ l = zipmapDecodeLength(p); + /* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */ + if (l < ZIPMAP_BIGLEN && s != 1) + return 0; + p += s; /* skip the encoded field size */ p += l; /* skip the field */ @@ -402,6 +406,9 @@ /* read the value length */ l = zipmapDecodeLength(p); + /* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */ + if (l < ZIPMAP_BIGLEN && s != 1) + return 0; p += s; /* skip the encoded value size*/ e = *p++; /* skip the encoded free space (always encoded in one byte) */ p += l+e; /* skip the value and free space */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/integration/corrupt-dump.tcl new/redis-8.6.3/tests/integration/corrupt-dump.tcl --- old/redis-8.6.2/tests/integration/corrupt-dump.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/integration/corrupt-dump.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -965,6 +965,89 @@ } } +test {corrupt payload: zipmap - element wouldn't fit in listpack} { + # Redis converts legacy zipmap encoded hashes to listpacks. + # This test creates a zipmap entry with a 1GB value which cannot + # fit into a listpack and verifies that RESTORE fails. + + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no proto-max-bulk-len 2147483648 client-query-buffer-limit 2147483648]] { + proc zipmap_encode_len {len} { + if {$len < 254} { + return [binary format c $len] + } else { + return [binary format ci 254 $len] + } + } + r config set sanitize-dump-payload no + + # Generates Zipmap with 1GB value - should fail lpSafeToAdd check + set val_len [expr {1024 * 1024 * 1024 + 1}] + + # Zipmap has 1 element + set zm [binary format c 1] + # Field is 1 byte long + append zm [zipmap_encode_len 1] + append zm "k" + # Value is 1GB long + append zm [zipmap_encode_len $val_len] + append zm [binary format c 0] + append zm [string repeat "A" $val_len] + # ZIPMAP_END marker + append zm [binary format c 255] + # Prepend RDB header + set zm_len [string length $zm] + set rdb_len [binary format cI 0x80 $zm_len] + set dump [binary format c 9] + append dump $rdb_len + append dump $zm + append dump [binary format s 9] + append dump [binary format w 0] + + catch {r RESTORE _hash 0 $dump} err + assert_match "*Bad data format*" $err + } +} {} {large-memory} + +test {corrupt payload: zipmap - 5 bytes length encoding for a small field} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no]] { + catch { + r restore key 0 "\x09\x11\x01\xfe\x04\x00\x00\x00\x01\x00\xff\x00\x04\x00\x76\x61\x6c\x31\xff\x09\x00\xf9\xd5\xa4\xf7\x7d\x00\x3f\x1b" + } err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 + } +} + +test {corrupt payload: zipmap - 5 bytes length encoding for a small value} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no]] { + catch { + r restore key 0 "\x09\x0e\x01\x01\x6b\xfe\x04\x00\x00\x00\x00\x76\x61\x6c\x31\xff\x09\x00\xd0\xf9\xe4\x1d\xe4\xfb\x11\x4c" + } err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 + } +} + +test {corrupt payload: zipmap - 5 bytes length encoding and a huge field} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { + catch { + r restore key 0 "\x09\x41\x15\x02\x04\x6b\x65\x79\x31\x04\x00\x76\x61\x6c\x31\xfe\x04\x00\x00\x00\xfe\xff\xff\xff\xfd\x00\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42 \x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\xff\x09\x00\x54\x2f\x0a\xca\x4e\x5c\x49\x9f" + } err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 + } +} + +test {corrupt payload: stream - duplicated consumer PEL entry} { + start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { + catch { + r restore key 0 "\x15\x01\x10\x00\x00\x01\x9b\x0d\x56\xa9\xb7\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x39\x40\x42\x15\x42\x00\x00\x00\x11\x00\x02\x01\x00\x01\x01\x01\x86\x66\x69\x65\x6c\x64\x31\x07\x00\x01\x40\x0f\x0a\x00\x01\x86\x76\x61\x6c\x75\x65\x31\x07\x04\x20\x0b\x02\xcd\xd9\x02\xe0\x01\x22\x01\x32\x07\x80\x1a\x04\x32\x07\x06\x01\xff\x02\x81\x00\x00\x01\x9b\x0d\x56\xb7\x90\x00\x81\x00\x00\x01\x9b\x0d\x56\xa9\xb7\x00\x00\x00\x02\x01\x07\x6d\x79\x67\x72\x6f\x75\x70\x81\x00\x00\x01\x9b\x0d\x56\xb7\x90\x00\x02\x02\x00\x00\x01\x9b\x0d\x56\xa9\xb7\x00\x00\x00\x00\x00\x00\x00\x00\x80\xd9\x56\x0d\x9b\x01\x00\x00\x01\x00\x00\x01\x9b\x0d\x56\xb7\x90\x00\x00\x00\x00\x00\x00\x00\x00\x80\xd9\x56\x0d\x9b\x01\x00\x00\x01\x01\x09\x63\x6f\x6e\x73\x75\x6d\x65\x72\x31\x80\xd9\x56\x0d\x9b\x01\x00\x00\x80\xd9\x56\x0d\x9b\x01\x00\x00\x02\x00\x00\x01\x9b\x0d\x56\xa9\xb7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x9b\x0d\x56\xa9\xb7\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x09\x00\x4b\xe0\x99\x30\x67\x4d\xe5\x87" + } err + assert_match "*Bad data format*" $err + verify_log_message 0 "*Duplicated consumer PEL entry*" 0 + } +} + } ;# tags diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/integration/replication.tcl new/redis-8.6.3/tests/integration/replication.tcl --- old/redis-8.6.2/tests/integration/replication.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/integration/replication.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -1872,3 +1872,80 @@ } } } + +# Fullsync should not free the functions lib ctx while the replica has +# a timed out function that is still running. +foreach type {script function} { + start_server {tags {"repl external:skip"}} { + start_server {} { + set master [srv -1 client] + set master_host [srv -1 host] + set master_port [srv -1 port] + set replica [srv 0 client] + + test "Fullsync should not free scripting engine on a replica while a $type is running" { + $master config set repl-diskless-sync yes + $master config set repl-diskless-sync-delay 0 + # Set small client output buffer limit to trigger fullsync quickly + $master config set client-output-buffer-limit "replica 1k 1k 0" + $replica config set repl-diskless-load yes + $replica config set busy-reply-threshold 1 ;# script timeout in 1 ms + + # Load function + if {$type eq "function"} { + $master function load replace {#!lua name=blocklib + redis.register_function{ + function_name='blockfunc', + callback=function() while true do end end, + flags={'no-writes'} + } + } + } + + # Start replication + $replica replicaof $master_host $master_port + wait_for_sync $replica + + # Run the blocking script on replica + set rd [redis_deferring_client] + if {$type eq "script"} { + $rd eval {while true do end} 0 + } else { + $rd fcall_ro blockfunc 0 + } + + # Verify replica replies with BUSY + wait_for_condition 50 100 { + [catch {$replica ping} e] == 1 && [string match {*BUSY*} $e] + } else { + fail "$type didn't become busy" + } + + # Fills client output buffer and triggers fullsync + populate 5 bigkey 1000000 -1 + wait_for_condition 50 100 { + [s -1 sync_full] >= 2 + } else { + fail "Fullsync was not triggered" + } + + # Verify replica is still running the function + after 1000 + catch {$replica ping} e + assert_match {*BUSY*} $e "replica should still reply with BUSY" + + if {$type eq "script"} { + $replica script kill + } else { + $replica function kill + } + + # Verify replica is responsive again + catch {$rd read} result + $rd close + wait_for_sync $replica + assert_equal [$replica ping] "PONG" + } + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/unit/client-eviction.tcl new/redis-8.6.3/tests/unit/client-eviction.tcl --- old/redis-8.6.2/tests/unit/client-eviction.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/unit/client-eviction.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -619,5 +619,34 @@ } } +start_server {} { + r flushall + r client no-evict on + r config set maxmemory-clients 0 + + test "Verify blocked client eviction during unblock does not cause use-after-free" { + # Create a deferring client that will be blocked on stream + # Use a long stream name to make client memory usage exceed 200000 bytes + set rd [redis_deferring_client] + $rd XREAD BLOCK 0 STREAMS mystream stream_[string repeat x 200000] $ $ + + # Wait for the client to be blocked + wait_for_condition 50 100 { + [s blocked_clients] eq {1} + } else { + fail "Client was not blocked" + } + + # Now lower MAXMEMORY-CLIENTS to a low value and use + # XADD to unblock the blocked client, triggering eviction. + r MULTI + r CONFIG SET MAXMEMORY-CLIENTS 100000 ;# Put in MULTI to defer blocked client eviction until after EXEC + r XADD mystream * field val + r EXEC + r PING + $rd close + } +} + } ;# tags diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/unit/cluster/announced-endpoints.tcl new/redis-8.6.3/tests/unit/cluster/announced-endpoints.tcl --- old/redis-8.6.2/tests/unit/cluster/announced-endpoints.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/unit/cluster/announced-endpoints.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -72,4 +72,88 @@ fail "Cluster announced port was not updated in cluster slots" } } + + # Tests for cluster-announce-ip validation + test "cluster-announce-ip validation" { + catch {R 0 config set cluster-announce-ip "192.168.1.100\nnext"} err + assert_match "*valid IPv4 or IPv6*" $err + + catch {R 0 config set cluster-announce-ip "10.0.0.1\ttab"} err + assert_match "*valid IPv4 or IPv6*" $err + + catch {R 0 config set cluster-announce-ip "1.2.3.4\r\n"} err + assert_match "*valid IPv4 or IPv6*" $err + + catch {R 0 config set cluster-announce-ip "redis-node-1.example.com"} err + assert_match "*valid IPv4 or IPv6*" $err + + catch {R 0 config set cluster-announce-ip "192.168.1"} err + assert_match "*valid IPv4 or IPv6*" $err + + # Accept valid IPv4 + R 0 config set cluster-announce-ip "192.168.1.100" + assert_equal "192.168.1.100" [lindex [R 0 config get cluster-announce-ip] 1] + + # Accept valid IPv6 + R 0 config set cluster-announce-ip "2001:db8::1" + assert_equal "2001:db8::1" [lindex [R 0 config get cluster-announce-ip] 1] + + # Can be cleared + R 0 config set cluster-announce-ip "" + assert_equal "" [lindex [R 0 config get cluster-announce-ip] 1] + } + + # Tests for cluster-announce-human-nodename validation + test "cluster-announce-human-nodename validation" { + # Reject control characters + catch {R 0 config set cluster-announce-human-nodename "badchar\nnext"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad\ttab"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad\r\nline"} err + assert_match "*invalid character*" $err + + # Reject delimiter characters (comma, equals, space) + catch {R 0 config set cluster-announce-human-nodename "bad,comma"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad=equals"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad space"} err + assert_match "*invalid character*" $err + + # Reject quote characters (double quote, single quote, backslash) + catch {R 0 config set cluster-announce-human-nodename "bad\"quote"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad'quote"} err + assert_match "*invalid character*" $err + + catch {R 0 config set cluster-announce-human-nodename "bad\\slash"} err + assert_match "*invalid character*" $err + + # Accept valid names + R 0 config set cluster-announce-human-nodename "my-redis-node-1" + assert_equal "my-redis-node-1" [lindex [R 0 config get cluster-announce-human-nodename] 1] + } + + # DoS prevention test: verify server can restart after CLUSTER SAVECONFIG + test "cluster-announce-ip persists correctly with CLUSTER SAVECONFIG" { + R 0 config set cluster-announce-ip "192.168.1.100" + R 0 cluster saveconfig + + # Verify the IP appears in CLUSTER NODES output + assert_match "*192.168.1.100*" [R 0 cluster nodes] + } + + test "cluster-announce-human-nodename persists correctly with CLUSTER SAVECONFIG" { + R 0 config set cluster-announce-human-nodename "production-node-1" + R 0 cluster saveconfig + + # Verify the nodename is set correctly + assert_equal "production-node-1" [lindex [R 0 config get cluster-announce-human-nodename] 1] + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/unit/dump.tcl new/redis-8.6.3/tests/unit/dump.tcl --- old/redis-8.6.2/tests/unit/dump.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/unit/dump.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -158,6 +158,19 @@ close_replication_stream $repl } {} {needs:repl} + test {RESTORE fail with invalid payload size} { + # Payload with mismatched size: claims 0xFFFFFFFFFFFFFFF7 bytes (max uint64 - 8) but provides no data + # \x00 = String type + # \x81 = 64-bit length marker + # \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF7 = 18446744073709551607 in big-endian + # \x0c\x00 = RDB version + # \x00... = fake CRC64 + set encoded "\x00\x81\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF7\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00" + r del test + catch {r restore test 0 $encoded} e + set e + } {*Bad data format*} + test {DUMP of non existing key returns nil} { r dump nonexisting_key } {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redis-8.6.2/tests/unit/pubsub.tcl new/redis-8.6.3/tests/unit/pubsub.tcl --- old/redis-8.6.2/tests/unit/pubsub.tcl 2026-03-24 13:22:41.000000000 +0100 +++ new/redis-8.6.3/tests/unit/pubsub.tcl 2026-05-05 15:23:21.000000000 +0200 @@ -1012,5 +1012,134 @@ assert_equal [r publish foo vaz] {1} assert_equal [r read] {message foo vaz} } {} {resp3} +} + +start_server {tags {"pubsub network"}} { + # Helper proc for tests that subscribe multiple times until hitting OOM + proc test_subscribe_oom_loop {cmd description clients} { + test "$cmd $description fails with OOM when memory limit exceeded" { + # Set 10MB memory limit + r config set maxmemory 10485760 + r config set maxmemory-policy noeviction + + # Create clients + if {$clients == 1} { + set rd [redis_deferring_client] + } else { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + } + + set base_str [string repeat "a" 2048] + set success_count 0 + set oom_occurred 0 + + # Try to subscribe until we hit OOM + for {set i 0} {$i < 5000} {incr i} { + # Select client + if {$clients == 1} { + set client $rd + } else { + set client [expr {$i % 2 ? $rd1 : $rd2}] + } + + # Build channel/pattern name + if {$cmd eq "psubscribe"} { + set channel_name "${base_str}${i}*" + } else { + set channel_name "${base_str}${i}" + } + + $client $cmd $channel_name + if {[catch {$client read} err]} { + if {[string match "*OOM command not allowed*" $err]} { + set oom_occurred 1 + break + } + error "Unexpected error: $err" + } + incr success_count + } + + # Verify we had at least one success and hit OOM + assert {$success_count > 10} + assert {$oom_occurred == 1} + + # Close clients + if {$clients == 1} { + $rd close + } else { + $rd1 close + $rd2 close + } + } + } + + # Helper proc for tests with single large channel that immediately fails + proc test_subscribe_large_channel_oom {cmd channel_type} { + test "$cmd with large $channel_type name fails due to OOM" { + # Set maxmemory to 2MB + r config set maxmemory 2097152 + r config set maxmemory-policy noeviction + + # Create large channel/pattern name: 2MB + set channel_name [string repeat "a" 2097152] + + # Create a single pubsub client + set rd [redis_deferring_client] + + # Subscribe should fail with OOM error + $rd $cmd $channel_name + assert_error "*OOM command not allowed when used memory > 'maxmemory'*" {$rd read} + + # Cleanup + $rd close + } + } + + # Helper proc for tests with small success then large failure + proc test_subscribe_small_then_large_oom {cmd channel_type} { + test "$cmd succeeds with small $channel_type but fails with large $channel_type due to OOM" { + # Set maxmemory to 5MB + r config set maxmemory 5242880 + r config set maxmemory-policy noeviction + + # Create channel names: first 10KB, second 5MB + set channel1 [string repeat "a" 10240] + set channel2 [string repeat "b" 5242880] + + # Create a single pubsub client + set rd [redis_deferring_client] + + # First subscribe should succeed (10KB) + $rd $cmd $channel1 + set reply1 [$rd read] + assert_equal [list $cmd] [lindex $reply1 0] + + # Second subscribe should fail with OOM error (5MB exceeds limit) + $rd $cmd $channel2 + assert_error "*OOM command not allowed when used memory > 'maxmemory'*" {$rd read} + + # Cleanup + $rd close + } + } + + # Multiple subscriptions until OOM tests + test_subscribe_oom_loop "subscribe" "" 1 + test_subscribe_oom_loop "ssubscribe" "" 1 + test_subscribe_oom_loop "psubscribe" "" 1 + test_subscribe_oom_loop "subscribe" "with 2 clients" 2 + test_subscribe_oom_loop "ssubscribe" "with 2 clients" 2 + test_subscribe_oom_loop "psubscribe" "with 2 clients" 2 + + # Single large channel immediate OOM tests + test_subscribe_large_channel_oom "subscribe" "channel" + test_subscribe_large_channel_oom "psubscribe" "pattern" + test_subscribe_large_channel_oom "ssubscribe" "shard channel" + # Small success then large failure tests + test_subscribe_small_then_large_oom "subscribe" "channel" + test_subscribe_small_then_large_oom "psubscribe" "pattern" + test_subscribe_small_then_large_oom "ssubscribe" "channel" } ++++++ redis.hashes ++++++ --- /var/tmp/diff_new_pack.7Q0dHy/_old 2026-05-06 19:18:37.302876416 +0200 +++ /var/tmp/diff_new_pack.7Q0dHy/_new 2026-05-06 19:18:37.306876581 +0200 @@ -221,4 +221,10 @@ hash redis-7.4.8.tar.gz sha256 f6773cb7d63be236c59c2917a82f1f08e47b77d89b2f0c9f53becb22b8ea4172 http://download.redis.io/releases/redis-7.4.8.tar.gz hash redis-7.2.13.tar.gz sha256 b3eeef15ea90a41c568f1f97a78d3370400baad55566869bbbf1af9f9106d85c http://download.redis.io/releases/redis-7.2.13.tar.gz hash redis-8.6.2.tar.gz sha256 cea46526594fe05f05b9ff733179eb1263deccf4269059cf081fdef222634c88 http://download.redis.io/releases/redis-8.6.2.tar.gz +hash redis-6.2.22.tar.gz sha256 3ce191a727505b2d711bb45cfde8d9eeab1e061bdc0e5cb56a50201069a9729d http://download.redis.io/releases/redis-6.2.22.tar.gz +hash redis-7.2.14.tar.gz sha256 21326da3f66c0aead4c8204c0ac52ff905337a77cadd169f75ac22835ea30025 http://download.redis.io/releases/redis-7.2.14.tar.gz +hash redis-7.4.9.tar.gz sha256 a71a67b47b2705d3448f0400573e3ad5c4c9f8c18236f426dc6acc7284bf42ad http://download.redis.io/releases/redis-7.4.9.tar.gz +hash redis-8.2.6.tar.gz sha256 78dd7326c5c959202c6c3849d3ea9c61896d78d647c20f6542b52c0917f96eac http://download.redis.io/releases/redis-8.2.6.tar.gz +hash redis-8.4.3.tar.gz sha256 aab9cc0f268354813d77ac29ae4fe86646f60590a21ea96a8d8caff38bd2efaa http://download.redis.io/releases/redis-8.4.3.tar.gz +hash redis-8.6.3.tar.gz sha256 9f54d4458c52be5472cdd1347d737f1d488b520fc3d0911cba47302de8d836e2 http://download.redis.io/releases/redis-8.6.3.tar.gz
