File CVE-2025-32023.patch of Package valkey.39739

From 8235d122779d427d6a3883201453305acf03607c Mon Sep 17 00:00:00 2001
From: Madelyn Olson <madelyneolson@gmail.com>
Date: Sun, 6 Jul 2025 23:20:32 +0300
Subject: [PATCH] Apply fixed for CVE-2025-32023 (#2314)

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
Co-authored-by: Madelyn Olson <madelyneolson@gmail.com>
---
 src/hyperloglog.c          | 47 ++++++++++++++++++++++++++++++++++----
 tests/unit/hyperloglog.tcl | 26 +++++++++++++++++++++
 2 files changed, 68 insertions(+), 5 deletions(-)

diff --git a/src/hyperloglog.c b/src/hyperloglog.c
index 79d81ac8fb..7c3b40e3a0 100644
--- a/src/hyperloglog.c
+++ b/src/hyperloglog.c
@@ -584,6 +584,7 @@ int hllSparseToDense(robj *o) {
     struct hllhdr *hdr, *oldhdr = (struct hllhdr *)sparse;
     int idx = 0, runlen, regval;
     uint8_t *p = (uint8_t *)sparse, *end = p + sdslen(sparse);
+    int valid = 1;
 
     /* If the representation is already the right one return ASAP. */
     hdr = (struct hllhdr *)sparse;
@@ -603,16 +604,27 @@ int hllSparseToDense(robj *o) {
     while (p < end) {
         if (HLL_SPARSE_IS_ZERO(p)) {
             runlen = HLL_SPARSE_ZERO_LEN(p);
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             idx += runlen;
             p++;
         } else if (HLL_SPARSE_IS_XZERO(p)) {
             runlen = HLL_SPARSE_XZERO_LEN(p);
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             idx += runlen;
             p += 2;
         } else {
             runlen = HLL_SPARSE_VAL_LEN(p);
             regval = HLL_SPARSE_VAL_VALUE(p);
-            if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             while (runlen--) {
                 HLL_DENSE_SET_REGISTER(hdr->registers, idx, regval);
                 idx++;
@@ -623,7 +635,7 @@ int hllSparseToDense(robj *o) {
 
     /* If the sparse representation was valid, we expect to find idx
      * set to HLL_REGISTERS. */
-    if (idx != HLL_REGISTERS) {
+    if (!valid || idx != HLL_REGISTERS) {
         sdsfree(dense);
         return C_ERR;
     }
@@ -920,27 +932,40 @@ int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
 void hllSparseRegHisto(uint8_t *sparse, int sparselen, int *invalid, int *reghisto) {
     int idx = 0, runlen, regval;
     uint8_t *end = sparse + sparselen, *p = sparse;
+    int valid = 1;
 
     while (p < end) {
         if (HLL_SPARSE_IS_ZERO(p)) {
             runlen = HLL_SPARSE_ZERO_LEN(p);
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             idx += runlen;
             reghisto[0] += runlen;
             p++;
         } else if (HLL_SPARSE_IS_XZERO(p)) {
             runlen = HLL_SPARSE_XZERO_LEN(p);
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             idx += runlen;
             reghisto[0] += runlen;
             p += 2;
         } else {
             runlen = HLL_SPARSE_VAL_LEN(p);
             regval = HLL_SPARSE_VAL_VALUE(p);
+            if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+                valid = 0;
+                break;
+            }
             idx += runlen;
             reghisto[regval] += runlen;
             p++;
         }
     }
-    if (idx != HLL_REGISTERS && invalid) *invalid = 1;
+    if ((!valid || idx != HLL_REGISTERS) && invalid) *invalid = 1;
 }
 
 /* ========================= HyperLogLog Count ==============================
@@ -1087,22 +1112,34 @@ int hllMerge(uint8_t *max, robj *hll) {
     } else {
         uint8_t *p = hll->ptr, *end = p + sdslen(hll->ptr);
         long runlen, regval;
+        int valid = 1;
 
         p += HLL_HDR_SIZE;
         i = 0;
         while (p < end) {
             if (HLL_SPARSE_IS_ZERO(p)) {
                 runlen = HLL_SPARSE_ZERO_LEN(p);
+                if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
+                    valid = 0;
+                    break;
+                }
                 i += runlen;
                 p++;
             } else if (HLL_SPARSE_IS_XZERO(p)) {
                 runlen = HLL_SPARSE_XZERO_LEN(p);
+                if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
+                    valid = 0;
+                    break;
+                }
                 i += runlen;
                 p += 2;
             } else {
                 runlen = HLL_SPARSE_VAL_LEN(p);
                 regval = HLL_SPARSE_VAL_VALUE(p);
-                if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
+                if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
+                    valid = 0;
+                    break;
+                }
                 while (runlen--) {
                     if (regval > max[i]) max[i] = regval;
                     i++;
@@ -1110,7 +1147,7 @@ int hllMerge(uint8_t *max, robj *hll) {
                 p++;
             }
         }
-        if (i != HLL_REGISTERS) return C_ERR;
+        if (!valid || i != HLL_REGISTERS) return C_ERR;
     }
     return C_OK;
 }
diff --git a/tests/unit/hyperloglog.tcl b/tests/unit/hyperloglog.tcl
index c1b3b3a79f..13836a0f19 100644
--- a/tests/unit/hyperloglog.tcl
+++ b/tests/unit/hyperloglog.tcl
@@ -1,4 +1,30 @@
 start_server {tags {"hll"}} {
+    test {CVE-2025-32023: Sparse HLL XZERO overflow triggers crash} {
+        # Build a valid HLL header for sparse encoding
+        set hll [binary format cccc 72 89 76 76] ; # "HYLL"
+        append hll [binary format cccc 1 0 0 0] ; # encoding=sparse, 3 reserved
+        append hll [binary format cccccccc 0 0 0 0 0 0 0 0] ; # cached cardinality
+
+        # We need alternating XZERO and ZERO opcodes to build up a large enough
+        # HyperLogLog value that will overflow the runlength.
+        # 0x7f 0xff is the XZERO opcode that sets the index to 16384.
+        # 0x3f is the ZERO opcode that sets the index to 256.
+        # We repeat this pattern at least 33554432 times to overflow the runlength,
+        # 50331648 is just 33554432 * 1.5, which is just a round number.
+        append hll [string repeat [binary format ccc 0x7f 0xff 0x3f] 50331648]
+
+        # We need an actual value at the end to trigger the out of bounds write
+        append hll [binary format c 0x80]
+
+        # Set the raw value into the key
+        r set hll_overflow $hll
+        r pfadd hll_merge_source hi
+
+        # Test pfadd and pfmerge, these used to crash but now error
+        assert_error {*INVALIDOBJ*} {r pfmerge fail_target hll_overflow hll_merge_source}
+        assert_error {*INVALIDOBJ*} {r pfadd hll_overflow foo}
+    } {} {large-memory}
+
     test {HyperLogLog self test passes} {
         catch {r pfselftest} e
         set e
openSUSE Build Service is sponsored by