diff --git a/meta-oe/recipes-extended/redis/redis-8.0.6/CVE-2026-25243.patch b/meta-oe/recipes-extended/redis/redis-8.0.6/CVE-2026-25243.patch new file mode 100644 index 0000000000..64349b14b3 --- /dev/null +++ b/meta-oe/recipes-extended/redis/redis-8.0.6/CVE-2026-25243.patch @@ -0,0 +1,275 @@ +From b9dde6fc25dec6191b18374335a076a7b31e3d02 Mon Sep 17 00:00:00 2001 +From: Sergei Georgiev +Date: Thu, 27 Nov 2025 11:41:11 +0200 +Subject: [PATCH] Invalid Memory Access in Redis RESTORE Command + (CVE-2026-25243) + +CVE: CVE-2026-25243 +Upstream-Status: Backport [https://github.com/redis/redis/commit/b9dde6fc25dec6191b18374335a076a7b31e3d02] + +Backport Changes: +- In src/rdb.c, the duplicate consumer PEL error path removes + streamFreeNACK(nack), using the 8.0.6 function signature; upstream removes + streamFreeNACK(s, nack). + +(cherry picked from commit b9dde6fc25dec6191b18374335a076a7b31e3d02) +Signed-off-by: Yogita Urade +--- + modules/vector-sets/vset.c | 46 +++++++++++++++-- + src/rdb.c | 6 +-- + src/sds.c | 9 +++- + src/zipmap.c | 7 +++ + tests/integration/corrupt-dump.tcl | 83 ++++++++++++++++++++++++++++++ + tests/unit/dump.tcl | 13 +++++ + 6 files changed, 155 insertions(+), 9 deletions(-) + +diff --git a/modules/vector-sets/vset.c b/modules/vector-sets/vset.c +index 75de69256..ec8e4f0a4 100644 +--- a/modules/vector-sets/vset.c ++++ b/modules/vector-sets/vset.c +@@ -1797,14 +1797,50 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) { + 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 dimensions to avoid absurd / degenerate matrices. */ ++ if (input_dim == 0 || output_dim == 0) { ++ RedisModule_LogIOError(rdb, "warning", ++ "Invalid projection matrix dimensions: input_dim=%u, output_dim=%u", ++ (unsigned)input_dim, (unsigned)output_dim); ++ goto ioerr; ++ } + +- // Load projection matrix as a binary blob +- char *matrix_blob = RedisModule_LoadStringBuffer(rdb, NULL); ++ /* Check for overflow in matrix_size = sizeof(float) * input_dim * output_dim. */ ++ #if SIZE_MAX == UINT32_MAX ++ if ((size_t)output_dim > SIZE_MAX / sizeof(float)) { ++ 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 ++ ++ size_t max_input = SIZE_MAX / (sizeof(float) * (size_t)output_dim); ++ if ((size_t)input_dim > max_input) { ++ RedisModule_LogIOError(rdb, "warning", ++ "Projection matrix size overflow: input_dim=%u, output_dim=%u", ++ (unsigned)input_dim, (unsigned)output_dim); ++ goto ioerr; ++ } ++ ++ 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 --git a/src/rdb.c b/src/rdb.c +index c09cd14bc..371353c2c 100644 +--- a/src/rdb.c ++++ b/src/rdb.c +@@ -2573,11 +2573,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) + + /* 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); +@@ -3068,7 +3069,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) + " loading a stream consumer " + "group"); + decrRefCount(o); +- streamFreeNACK(nack); + return NULL; + } + } +diff --git a/src/sds.c b/src/sds.c +index 7f1429c46..edd0495aa 100644 +--- a/src/sds.c ++++ b/src/sds.c +@@ -90,7 +90,14 @@ sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) { + unsigned char *fp; /* flags pointer. */ + size_t usable; + +- 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, &usable) : + s_malloc_usable(hdrlen+initlen+1, &usable); +diff --git a/src/zipmap.c b/src/zipmap.c +index 51c64ca81..e3981d810 100644 +--- a/src/zipmap.c ++++ b/src/zipmap.c +@@ -387,6 +387,10 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) { + + /* 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 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) { + + /* 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 --git a/tests/integration/corrupt-dump.tcl b/tests/integration/corrupt-dump.tcl +index ee044c71a..6017723e0 100644 +--- a/tests/integration/corrupt-dump.tcl ++++ b/tests/integration/corrupt-dump.tcl +@@ -941,6 +941,89 @@ test {corrupt payload: fuzzer findings - vector sets with wrong encoding} { + } + } + ++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 --git a/tests/unit/dump.tcl b/tests/unit/dump.tcl +index 0cd62804a..1ce9bc846 100644 +--- a/tests/unit/dump.tcl ++++ b/tests/unit/dump.tcl +@@ -140,6 +140,19 @@ start_server {tags {"dump"}} { + 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 --git a/meta-oe/recipes-extended/redis/redis_8.0.6.bb b/meta-oe/recipes-extended/redis/redis_8.0.6.bb index a83ffbae48..5d4ca143d1 100644 --- a/meta-oe/recipes-extended/redis/redis_8.0.6.bb +++ b/meta-oe/recipes-extended/redis/redis_8.0.6.bb @@ -18,6 +18,7 @@ SRC_URI = "http://download.redis.io/releases/${BP}.tar.gz \ file://CVE-2026-81934.patch \ file://CVE-2026-23479.patch \ file://CVE-2026-23631.patch \ + file://CVE-2026-25243.patch \ " SRC_URI[sha256sum] = "6d0a9913887a4972536f9da226f1575859c34d86354129163260a5f9c6bd4229"