Package: release.debian.org Severity: normal X-Debbugs-Cc: [email protected] Control: affects -1 + src:redict User: [email protected] 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 <[email protected]> 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 <[email protected]> +From: fossdd <[email protected]> +Acked-by: Maytham Alsudany <[email protected]> +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 <[email protected]> +From: Maytham Alsudany <[email protected]> +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

