diff mbox series

[meta-oe,wrynose,3/4] redis: fix CVE-2026-25243

Message ID 20261016052110.184934-3-yurade@cisco.com
State New
Headers show
Series [meta-oe,wrynose,1/4] redis: fix CVE-2026-23479 | expand

Commit Message

Yogita Urade Oct. 16, 2026, 5:21 a.m. UTC
This patch applies the upstream fix as referenced in [2],
using the commit shown in [1].

[1] https://github.com/redis/redis/commit/b9dde6fc25dec6191b18374335a076a7b31e3d02
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-25243

Signed-off-by: Yogita Urade <yurade@cisco.com>
---
 .../redis/redis-8.0.6/CVE-2026-25243.patch    | 275 ++++++++++++++++++
 meta-oe/recipes-extended/redis/redis_8.0.6.bb |   1 +
 2 files changed, 276 insertions(+)
 create mode 100644 meta-oe/recipes-extended/redis/redis-8.0.6/CVE-2026-25243.patch
diff mbox series

Patch

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 <s_ggeorgiev@yahoo.com>
+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 <yurade@cisco.com>
+---
+ 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"