Bug#1110319: unblock: redict/7.3.5+ds-1
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: redict@packages.debian.org
Control: affects -1 + src:redict
User: release.debian.org@packages.debian.org
Usertags: unblock
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Please unblock package redict
[ Reason ]
RC bugs:
* Fix CVE-2025-21605 (Closes: #1104011)
* Fix CVE-2025-48367 (Closes: #1108980)
* Fix CVE-2025-32023 (Closes: #1108977)
* Fix CVE-2025-27151 (Closes: #1106823)
and an important bug:
* Fix CVE-2025-49112 (Closes: #1107212)
[ Impact ]
Redict won't be in Trixie.
[ Tests ]
Upstream's tests are extensive and continue to pass.
[ Risks ]
Leaf package. Same/similar fixes are already applied for redis and
valkey packages.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
[ Other info ]
debdiff might be hard to digest because it contains a new upstream
version; summary included below. Upstream only publishes security fixes,
there are no new features that could potentially break anything.
Upstream changes:
( https://codeberg.org/redict/redict/compare/7.3.2...7.3.5 )
* d73a7eac9b Limiting output buffer for unauthenticated client (CVE-2025-21605)
* b75220c7e1 tests: Fix redict test command
* f7cd2acc2a Retry accept(2) on transient errors (CVE-2025-48367)
* 682ea40774 Fix CVE-2025-32023
* and various version number bumps
Debian changes:
* Patch to fix CVE-2025-27151
https://salsa.debian.org/redict-team/redict/-/blob/debian/7.3.5+ds-1/debian/patches/0006-CVE-2025-27151.patch
* Patch to fix CVE-2025-49112
https://salsa.debian.org/redict-team/redict/-/blob/debian/7.3.5+ds-1/debian/patches/0007-CVE-2025-49112.patch
unblock redict/7.3.5+ds-1
-----BEGIN PGP SIGNATURE-----
iQJHBAEBCgAxFiEESl/RzRFQh8wD3DXB1ZeJcgbF8H8FAmiPE0MTHG1heXRoYW1A
ZGViaWFuLm9yZwAKCRDVl4lyBsXwf/hBD/4rWzwyFdrvuu1zFR9oGAYNjzGbOYi+
tG6iUsKkCcGyt/Ug2iE4mDpiaQOpdRIXd1RgkvchZ3ffT9Cs+EPO7r9+OgOeQMxX
qx9j+S4ehnDs+0g5IQpAtsRuKV9ajEFJeMxGVxC0XUZlEUKfok9TJBLw1oW8jeDA
7Us0V+kSDCrWgeDCpIErA48ZihP9hRG0GzChD30S57pLy61Y7zBd2s5Mrg+ovk+z
xTKp2qqch5Rsmw5N6TvkkrZ+eiRPPmoLp5xNA/Pm3JEl2lA6bxBlzdYWcv5IUNX4
nydEZYVm21OTnBCvEOgKNijuXhshwJwa6tDSNuosoFL59CYUZbIRiFfPN+s8yl9y
7ulbnY0TGAiIggt50tr0UcySTCcvCJnBx/1uZD4q9Cu+EWTSo6lXgmOg3Xv90Wyh
l0OTnrcOQgOnWEWxP/Wcw/E33IYy6A5x2CP+CmLQgHjqNCy8bMh61Z3dvVtHWc+E
/TBa6eUVZpmRqbNSXKvH2UZ+yjpIczvlfULZqY9D1p0I/8TeyEHZ0adBaUPFEq/u
v7Jd9njqmn8AWOLUpglP55plbnQUOMu2neF3+AsuSPIxYxM8IzKVj3hM90K0w1z9
trS0t7ebkl5Qujhbzj2vOzCeO4mXmLqyQ/iC4bp4JS/iwqo52/M8eHHQ3dezizAB
4nZV1sKuzjgpvg==
=XHBu
-----END PGP SIGNATURE-----
diff -Nru redict-7.3.2+ds/debian/changelog redict-7.3.5+ds/debian/changelog
--- redict-7.3.2+ds/debian/changelog 2025-01-08 21:10:54.000000000 +0800
+++ redict-7.3.5+ds/debian/changelog 2025-08-03 14:22:26.000000000 +0800
@@ -1,3 +1,14 @@
+redict (7.3.5+ds-1) unstable; urgency=medium
+
+ * New upstream version 7.3.5
+ * Contains fix for CVE-2025-21605 (Closes: #1104011)
+ * Contains fix for CVE-2025-48367 (Closes: #1108980)
+ * Contains fix for CVE-2025-32023 (Closes: #1108977)
+ * Add patch to fix CVE-2025-27151 (Closes: #1106823)
+ * Add patch to fix CVE-2025-49112 (Closes: #1107212)
+
+ -- Maytham Alsudany <maytham@debian.org> Sun, 03 Aug 2025 14:22:26 +0800
+
redict (7.3.2+ds-1) unstable; urgency=medium
* New upstream version 7.3.2
diff -Nru redict-7.3.2+ds/debian/gbp.conf redict-7.3.5+ds/debian/gbp.conf
--- redict-7.3.2+ds/debian/gbp.conf 2024-10-13 08:27:29.000000000 +0800
+++ redict-7.3.5+ds/debian/gbp.conf 2025-08-03 14:18:23.000000000 +0800
@@ -1,3 +1,4 @@
[DEFAULT]
debian-branch=debian/latest
upstream-branch=upstream
+ignore-branch=True
diff -Nru redict-7.3.2+ds/debian/patches/0006-CVE-2025-27151.patch redict-7.3.5+ds/debian/patches/0006-CVE-2025-27151.patch
--- redict-7.3.2+ds/debian/patches/0006-CVE-2025-27151.patch 1970-01-01 08:00:00.000000000 +0800
+++ redict-7.3.5+ds/debian/patches/0006-CVE-2025-27151.patch 2025-08-03 14:22:26.000000000 +0800
@@ -0,0 +1,26 @@
+From: YaacovHazan <yaacov.hazan@redis.com>
+From: fossdd <fossdd@pwned.life>
+Acked-by: Maytham Alsudany <maytham@debian.org>
+Subject: [PATCH] Check length of AOF file name in redict-check-aof (CVE-2025-27151)
+Applied-Upstream: https://codeberg.org/redict/redict/commit/40aa98db1d6601d30154ff078705dcfe1c4c7708
+Bug-Debian: https://bugs.debian.org/1106823
+
+Adapted from https://github.com/redis/redis/commit/643b5db235cb82508e72f11c7b4bbfc7dc39be56
+
+Ensure that the length of the input file name does not exceed PATH_MAX
+
+--- a/src/redict-check-aof.c
++++ b/src/redict-check-aof.c
+@@ -534,6 +534,12 @@ int redict_check_aof_main(int argc, char **argv) {
+ goto invalid_args;
+ }
+
++ /* Check if filepath is longer than PATH_MAX */
++ if (strlen(filepath) > PATH_MAX) {
++ printf("Error: filepath is too long (exceeds PATH_MAX)\n");
++ goto invalid_args;
++ }
++
+ /* In the glibc implementation dirname may modify their argument. */
+ memcpy(temp_filepath, filepath, strlen(filepath) + 1);
+ dirpath = dirname(temp_filepath);
diff -Nru redict-7.3.2+ds/debian/patches/0007-CVE-2025-49112.patch redict-7.3.5+ds/debian/patches/0007-CVE-2025-49112.patch
--- redict-7.3.2+ds/debian/patches/0007-CVE-2025-49112.patch 1970-01-01 08:00:00.000000000 +0800
+++ redict-7.3.5+ds/debian/patches/0007-CVE-2025-49112.patch 2025-08-03 14:22:26.000000000 +0800
@@ -0,0 +1,31 @@
+From: Zeroday BYTE <github@zerodaysec.org>
+From: Maytham Alsudany <maytham@debian.org>
+Subject: [PATCH] Fix unsigned difference expression compared to zero (#2101)
+Forwarded: no
+Bug-Debian: https://bugs.debian.org/1107212
+
+Adapted from https://github.com/valkey-io/valkey/commit/374718b2a365ca69f715d542709b7d71540b1387
+
+Fix the issue need to ensure that the subtraction `prev->size -
+prev->used` does not underflow. This can be achieved by explicitly
+checking that `prev->used` is less than `prev->size` before performing
+the subtraction. This approach avoids relying on unsigned arithmetic and
+ensures the logic is clear and robust.
+
+The specific changes are:
+1. Replace the condition `prev->size - prev->used > 0` with `prev->used
+< prev->size`.
+2. This change ensures that the logic checks whether there is remaining
+space in the buffer without risking underflow.
+
+--- a/src/networking.c
++++ b/src/networking.c
+@@ -756,7 +756,7 @@
+ * - It has enough room already allocated
+ * - And not too large (avoid large memmove) */
+ if (ln->prev != NULL && (prev = listNodeValue(ln->prev)) &&
+- prev->size - prev->used > 0)
++ prev->used < prev->size)
+ {
+ size_t len_to_copy = prev->size - prev->used;
+ if (len_to_copy > length)
diff -Nru redict-7.3.2+ds/debian/patches/series redict-7.3.5+ds/debian/patches/series
--- redict-7.3.2+ds/debian/patches/series 2024-10-13 08:27:29.000000000 +0800
+++ redict-7.3.5+ds/debian/patches/series 2025-08-03 14:22:26.000000000 +0800
@@ -4,3 +4,5 @@
0003-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch
0004-Add-support-for-USE_SYSTEM_HIREDICT-flag.patch
0005-Fix-hiredict-imports.patch
+0006-CVE-2025-27151.patch
+0007-CVE-2025-49112.patch
diff -Nru redict-7.3.2+ds/src/anet.c redict-7.3.5+ds/src/anet.c
--- redict-7.3.2+ds/src/anet.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/anet.c 2025-07-14 16:50:50.000000000 +0800
@@ -596,6 +596,37 @@
return s;
}
+/* For some error cases indicates transient errors and accept can be retried
+ * in order to serve other pending connections. This function should be called with the last errno,
+ * right after anetTcpaccept or anetUnixAccept returned an error in order to retry them. */
+int anetRetryAcceptOnError(int err) {
+ /* This is a transient error which can happen, for example, when
+ * a client initiates a TCP handshake (SYN),
+ * the server receives and queues it in the pending connections queue (the SYN queue),
+ * but before accept() is called, the connection is aborted.
+ * in such cases we can continue accepting other connections. ß*/
+ if (err == ECONNABORTED)
+ return 1;
+
+#if defined(__linux__)
+ /* https://www.man7.org/linux/man-pages/man2/accept4.2 suggests that:
+ * Linux accept() (and accept4()) passes already-pending network
+ errors on the new socket as an error code from accept(). This
+ behavior differs from other BSD socket implementations. For
+ reliable operation the application should detect the network
+ errors defined for the protocol after accept() and treat them like
+ EAGAIN by retrying. In the case of TCP/IP, these are ENETDOWN,
+ EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP,
+ and ENETUNREACH. */
+ if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT ||
+ err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH ||
+ err == EOPNOTSUPP || err == ENETUNREACH) {
+ return 1;
+ }
+#endif
+ return 0;
+}
+
/* Accept a connection and also make sure the socket is non-blocking, and CLOEXEC.
* returns the new socket FD, or -1 on error. */
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
diff -Nru redict-7.3.2+ds/src/anet.h redict-7.3.5+ds/src/anet.h
--- redict-7.3.2+ds/src/anet.h 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/anet.h 2025-07-14 16:50:50.000000000 +0800
@@ -50,5 +50,6 @@
int anetSetSockMarkId(char *err, int fd, uint32_t id);
int anetGetError(int fd);
int anetIsFifo(char *filepath);
+int anetRetryAcceptOnError(int err);
#endif
diff -Nru redict-7.3.2+ds/src/cluster_legacy.c redict-7.3.5+ds/src/cluster_legacy.c
--- redict-7.3.2+ds/src/cluster_legacy.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/cluster_legacy.c 2025-07-14 16:50:50.000000000 +0800
@@ -800,9 +800,9 @@
void deriveAnnouncedPorts(int *announced_tcp_port, int *announced_tls_port,
int *announced_cport) {
/* Config overriding announced ports. */
- *announced_tcp_port = server.cluster_announce_port ?
+ *announced_tcp_port = server.cluster_announce_port ?
server.cluster_announce_port : server.port;
- *announced_tls_port = server.cluster_announce_tls_port ?
+ *announced_tls_port = server.cluster_announce_tls_port ?
server.cluster_announce_tls_port : server.tls_port;
/* Derive cluster bus port. */
if (server.cluster_announce_bus_port) {
@@ -891,7 +891,7 @@
} else if (!new && (sdslen(node->human_nodename) == 0)) {
return;
}
-
+
if (new) {
node->human_nodename = sdscpy(node->human_nodename, new);
} else if (sdslen(node->human_nodename) != 0) {
@@ -1031,7 +1031,7 @@
serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", listener->port);
exit(1);
}
-
+
if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) {
serverPanic("Unrecoverable error creating Redict Cluster socket accept handler.");
}
@@ -1237,6 +1237,7 @@
while(max--) {
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
+ if (anetRetryAcceptOnError(errno)) continue;
if (errno != EWOULDBLOCK)
serverLog(LL_VERBOSE,
"Error accepting cluster node: %s", server.neterr);
@@ -2027,7 +2028,7 @@
char *getCorruptedNodeIdByteString(clusterMsgDataGossip *gossip_msg) {
const int num_bytes = CLUSTER_NAMELEN + 8;
/* Allocate enough room for 4 chars per byte + null terminator */
- char *byte_string = (char*) zmalloc((num_bytes*4) + 1);
+ char *byte_string = (char*) zmalloc((num_bytes*4) + 1);
const char *name_ptr = gossip_msg->nodename;
/* Ensure we won't print beyond the bounds of the message */
@@ -2255,7 +2256,7 @@
if (node->link) freeClusterLink(node->link);
node->flags &= ~CLUSTER_NODE_NOADDR;
serverLog(LL_NOTICE,"Address updated for node %.40s (%s), now %s:%d",
- node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node));
+ node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node));
/* Check if this is our master and we have to change the
* replication target as well. */
@@ -2436,7 +2437,7 @@
* The ping/pong/meet messages support arbitrary extensions to add additional
* metadata to the messages that are sent between the various nodes in the
* cluster. The extensions take the form:
- * [ Header length + type (8 bytes) ]
+ * [ Header length + type (8 bytes) ]
* [ Extension information (Arbitrary length, but must be 8 byte padded) ]
*/
@@ -2451,7 +2452,7 @@
static clusterMsgPingExt *getInitialPingExt(clusterMsg *hdr, int count) {
clusterMsgPingExt *initial = (clusterMsgPingExt*) &(hdr->data.ping.gossip[count]);
return initial;
-}
+}
/* Given a current ping extension, returns the start of the next extension. May return
* an invalid address if there are no further ping extensions. */
@@ -2533,7 +2534,7 @@
/* Populate human_nodename */
clusterMsgPingExtHumanNodename *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, getHumanNodenamePingExtSize());
memcpy(ext->human_nodename, myself->human_nodename, sdslen(myself->human_nodename));
-
+
/* Move the write cursor */
cursor = nextPingExt(cursor);
}
@@ -2974,7 +2975,7 @@
if (sender->configEpoch > senderConfigEpoch) {
serverLog(LL_NOTICE,
"Ignore stale message from %.40s (%s) in shard %.40s;"
- " gossip config epoch: %llu, current config epoch: %llu",
+ " gossip config epoch: %llu, current config epoch: %llu",
sender->name,
sender->human_nodename,
sender->shard_id,
@@ -3758,7 +3759,7 @@
decrRefCount(channel);
decrRefCount(message);
-
+
return msgblock;
}
@@ -4119,13 +4120,13 @@
}
lastlog_time = time(NULL);
serverLog(LL_NOTICE,"Currently unable to failover: %s", msg);
-
+
int cur_vote = server.cluster->failover_auth_count;
int cur_quorum = (server.cluster->size / 2) + 1;
/* Emits a log when an election is in progress and waiting for votes or when the failover attempt expired. */
if (reason == CLUSTER_CANT_FAILOVER_WAITING_VOTES || reason == CLUSTER_CANT_FAILOVER_EXPIRED) {
serverLog(LL_NOTICE, "Needed quorum: %d. Number of votes received so far: %d", cur_quorum, cur_vote);
- }
+ }
}
/* This function implements the final part of automatic and manual failovers,
@@ -4638,7 +4639,7 @@
*/
if(clusterNodeCronHandleReconnect(node, handshake_timeout, now)) continue;
}
- dictReleaseIterator(di);
+ dictReleaseIterator(di);
/* Ping some random node 1 time every 10 iterations, so that we usually ping
* one random node every second. */
@@ -4724,7 +4725,7 @@
* received PONG is older than half the cluster timeout, send
* a new ping now, to ensure all the nodes are pinged without
* a too big delay. */
- mstime_t ping_interval = server.cluster_ping_interval ?
+ mstime_t ping_interval = server.cluster_ping_interval ?
server.cluster_ping_interval : server.cluster_node_timeout/2;
if (node->link &&
node->ping_sent == 0 &&
@@ -4766,7 +4767,7 @@
node->flags |= CLUSTER_NODE_PFAIL;
update_state = 1;
if (clusterNodeIsMaster(myself) && server.cluster->size == 1) {
- markNodeAsFailingIfNeeded(node);
+ markNodeAsFailingIfNeeded(node);
} else {
serverLog(LL_DEBUG,"*** NODE %.40s possibly failing", node->name);
}
@@ -5417,7 +5418,7 @@
* include all the known nodes in the representation, including nodes in
* the HANDSHAKE state.
*
- * Setting tls_primary to 1 to put TLS port in the main <ip>:<port>
+ * Setting tls_primary to 1 to put TLS port in the main <ip>:<port>
* field and put TCP port in aux field, instead of the opposite way.
*
* The representation obtained using this function is used for the output
@@ -5568,7 +5569,7 @@
for (j = 0; j < CLUSTER_SLOTS; j++) {
if (slots[j]) {
int retval;
-
+
/* If this slot was set as importing we can clear this
* state as now we are the real owner of the slot. */
if (server.cluster->importing_slots_from[j])
diff -Nru redict-7.3.2+ds/src/hyperloglog.c redict-7.3.5+ds/src/hyperloglog.c
--- redict-7.3.2+ds/src/hyperloglog.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/hyperloglog.c 2025-07-14 16:50:50.000000000 +0800
@@ -562,7 +562,8 @@
sds sparse = o->ptr, dense;
struct hllhdr *hdr, *oldhdr = (struct hllhdr*)sparse;
int idx = 0, runlen, regval;
- uint8_t *p = (uint8_t*)sparse, *end = p+sdslen(sparse);
+ 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;
@@ -582,18 +583,29 @@
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. */
- while(runlen--) {
- HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
+ if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
+ valid = 0;
+ break;
+ }
+ while (runlen--) {
+ HLL_DENSE_SET_REGISTER(hdr->registers, idx, regval);
idx++;
}
p++;
@@ -602,7 +614,7 @@
/* 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;
}
@@ -898,28 +910,41 @@
/* Compute the register histogram in the sparse representation. */
void hllSparseRegHisto(uint8_t *sparse, int sparselen, int *invalid, int* reghisto) {
int idx = 0, runlen, regval;
- uint8_t *end = sparse+sparselen, *p = sparse;
+ 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 ==============================
@@ -1067,30 +1092,42 @@
} 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. */
- while(runlen--) {
+ if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
+ valid = 0;
+ break;
+ }
+ while (runlen--) {
if (regval > max[i]) max[i] = regval;
i++;
}
p++;
}
}
- if (i != HLL_REGISTERS) return C_ERR;
+ if (!valid || i != HLL_REGISTERS) return C_ERR;
}
return C_OK;
}
diff -Nru redict-7.3.2+ds/src/networking.c redict-7.3.5+ds/src/networking.c
--- redict-7.3.2+ds/src/networking.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/networking.c 2025-07-14 16:50:50.000000000 +0800
@@ -3877,6 +3877,11 @@
int soft = 0, hard = 0, class;
unsigned long used_mem = getClientOutputBufferMemoryUsage(c);
+ /* For unauthenticated clients the output buffer is limited to prevent
+ * them from abusing it by not reading the replies */
+ if (used_mem > 1024 && authRequired(c))
+ return 1;
+
class = getClientType(c);
/* For the purpose of output buffer limiting, masters are handled
* like normal clients. */
diff -Nru redict-7.3.2+ds/src/socket.c redict-7.3.5+ds/src/socket.c
--- redict-7.3.2+ds/src/socket.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/socket.c 2025-07-14 16:50:50.000000000 +0800
@@ -297,6 +297,7 @@
while(max--) {
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
+ if (anetRetryAcceptOnError(errno)) continue;
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", server.neterr);
diff -Nru redict-7.3.2+ds/src/tls.c redict-7.3.5+ds/src/tls.c
--- redict-7.3.2+ds/src/tls.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/tls.c 2025-07-14 16:50:50.000000000 +0800
@@ -754,6 +754,7 @@
while(max--) {
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
+ if (anetRetryAcceptOnError(errno)) continue;
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", server.neterr);
diff -Nru redict-7.3.2+ds/src/unix.c redict-7.3.5+ds/src/unix.c
--- redict-7.3.2+ds/src/unix.c 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/unix.c 2025-07-14 16:50:50.000000000 +0800
@@ -82,6 +82,7 @@
while(max--) {
cfd = anetUnixAccept(server.neterr, fd);
if (cfd == ANET_ERR) {
+ if (anetRetryAcceptOnError(errno)) continue;
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", server.neterr);
diff -Nru redict-7.3.2+ds/src/version.h redict-7.3.5+ds/src/version.h
--- redict-7.3.2+ds/src/version.h 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/src/version.h 2025-07-14 16:50:50.000000000 +0800
@@ -4,5 +4,5 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: LGPL-3.0-only
-#define REDICT_VERSION "7.3.2"
-#define REDICT_VERSION_NUM 0x00070302
+#define REDICT_VERSION "7.3.5"
+#define REDICT_VERSION_NUM 0x00070305
diff -Nru redict-7.3.2+ds/tests/unit/auth.tcl redict-7.3.5+ds/tests/unit/auth.tcl
--- redict-7.3.2+ds/tests/unit/auth.tcl 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/tests/unit/auth.tcl 2025-07-14 16:50:50.000000000 +0800
@@ -51,6 +51,24 @@
assert_match {*unauthenticated bulk length*} $e
$rr close
}
+
+ test {For unauthenticated clients output buffer is limited} {
+ set rr [redict [srv "host"] [srv "port"] 1 $::tls]
+ $rr SET x 5
+ catch {[$rr read]} e
+ assert_match {*NOAUTH Authentication required*} $e
+
+ # Fill the output buffer in a loop without reading it and make
+ # sure the client disconnected.
+ # Considering the socket eat some of the replies, we are testing
+ # that such client can't consume more than few MB's.
+ catch {
+ for {set j 0} {$j < 1000000} {incr j} {
+ $rr SET x 5
+ }
+ } e
+ assert_match {I/O error reading reply} $e
+ }
}
start_server {tags {"auth_binary_password external:skip"}} {
@@ -82,7 +100,7 @@
# Verify replica is not able to sync with master
wait_for_log_messages 0 {"*Unable to AUTH to MASTER*"} $loglines 1000 10
assert_equal {down} [s 0 master_link_status]
-
+
# Test replica with the correct masterauth
$slave config set masterauth "abc\x00def"
wait_for_condition 50 100 {
diff -Nru redict-7.3.2+ds/tests/unit/hyperloglog.tcl redict-7.3.5+ds/tests/unit/hyperloglog.tcl
--- redict-7.3.2+ds/tests/unit/hyperloglog.tcl 2025-01-08 17:09:07.000000000 +0800
+++ redict-7.3.5+ds/tests/unit/hyperloglog.tcl 2025-07-14 16:50:50.000000000 +0800
@@ -5,6 +5,32 @@
# SPDX-License-Identifier: LGPL-3.0-only
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
Reply to: