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