Version in base suite: 8.0.2-3+deb13u1 Base version: redis_8.0.2-3+deb13u1 Target version: redis_8.0.2-3+deb13u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/r/redis/redis_8.0.2-3+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/r/redis/redis_8.0.2-3+deb13u2.dsc changelog | 12 + patches/0012-CVE-2025-67733.patch | 237 ++++++++++++++++++++++++++++++++++++++ patches/0013-CVE-2026-21863.patch | 189 ++++++++++++++++++++++++++++++ patches/series | 2 4 files changed, 440 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpc9wpbxtj/redis_8.0.2-3+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpc9wpbxtj/redis_8.0.2-3+deb13u2.dsc: no acceptable signature found diff -Nru redis-8.0.2/debian/changelog redis-8.0.2/debian/changelog --- redis-8.0.2/debian/changelog 2025-10-07 18:00:58.000000000 +0000 +++ redis-8.0.2/debian/changelog 2026-05-13 04:00:00.000000000 +0000 @@ -1,3 +1,15 @@ +redis (5:8.0.2-3+deb13u2) trixie-security; urgency=high + + * CVE-2025-67733: RESP protocol injection via Lua error_reply. A user + could manipulate data read by a connection by injecting CR/LF + sequences into a Redis error reply. + * CVE-2026-21863: Remote DoS with malformed Cluster bus message. A peer + could send a crafted PING/PONG/MEET packet whose gossip count or + ping-extension header exceeds the received packet length, causing + out-of-bounds reads and a server crash. + + -- Aron Xu Wed, 13 May 2026 12:00:00 +0800 + redis (5:8.0.2-3+deb13u1) trixie-security; urgency=medium * CVE-2025-49844 / CVE-2025-46819 / CVE-2025-46818 / CVE-2025-46817 diff -Nru redis-8.0.2/debian/patches/0012-CVE-2025-67733.patch redis-8.0.2/debian/patches/0012-CVE-2025-67733.patch --- redis-8.0.2/debian/patches/0012-CVE-2025-67733.patch 1970-01-01 00:00:00.000000000 +0000 +++ redis-8.0.2/debian/patches/0012-CVE-2025-67733.patch 2026-05-13 04:00:00.000000000 +0000 @@ -0,0 +1,237 @@ +From 6910256443c74057e0d83e08c61ea0021774fa6f Mon Sep 17 00:00:00 2001 +From: "debing.sun" +Date: Sun, 28 Dec 2025 15:37:48 +0800 +Subject: [PATCH] Strip CRLF from error and simple string replies (#826) + +Because in some cases, the client put \r\n in the command parameters. +When Redis returns these parameters to the client via an error reply, the presence of \r\n in the middle can cause the client to only parse the portion before the \r\n when handling the error reply. This disrupts the protocol parsing and ultimately causes the connection to become stuck. + +[Backport from https://github.com/redis/redis/commit/6910256443c74057e0d83e08c61ea0021774fa6f] + +[Adapted for redis-8.0.2: + - `addReplyErrorSdsSafe` already exists in redis-8.0.2 src/networking.c + and src/server.h, so the networking.c hunk only adds the new + `addReplyErrorSdsExSafe` and `addReplyStatusSafe` helpers and the + addReplyStatusFormat CRLF-stripping, and the server.h hunk only adds + declarations for the two new helpers (the existing + `addReplyErrorSdsSafe` declaration is left in place). + - Hunk offsets/context in src/functions.c, src/script_lua.c, + src/module.c, tests/modules/reply.c, tests/unit/functions.tcl, + tests/unit/moduleapi/reply.tcl and tests/unit/scripting.tcl adjusted + for redis-8.0.2 line numbering. Fix logic byte-identical to upstream.] +--- + src/functions.c | 2 +- + src/module.c | 4 +--- + src/networking.c | 19 +++++++++++++++++++ + src/script_lua.c | 7 ++----- + src/server.h | 2 ++ + tests/modules/reply.c | 15 +++++++++++++++ + tests/unit/functions.tcl | 9 +++++++++ + tests/unit/moduleapi/reply.tcl | 16 ++++++++++++++++ + tests/unit/scripting.tcl | 8 ++++++++ + 9 files changed, 73 insertions(+), 9 deletions(-) + +diff --git a/src/functions.c b/src/functions.c +--- a/src/functions.c ++++ b/src/functions.c +@@ -517,7 +517,7 @@ void functionListCommand(client *c) { + library_name = c->argv[++i]->ptr; + continue; + } +- addReplyErrorSds(c, sdscatfmt(sdsempty(), "Unknown argument %s", next_arg->ptr)); ++ addReplyErrorSdsSafe(c, sdscatfmt(sdsempty(), "Unknown argument %s", next_arg->ptr)); + return; + } + size_t reply_len = 0; +diff --git a/src/module.c b/src/module.c +--- a/src/module.c ++++ b/src/module.c +@@ -3150,9 +3150,7 @@ int RM_ReplyWithErrorFormat(RedisModuleCtx *ctx, const char *fmt, ...) { + int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) { + client *c = moduleGetReplyClient(ctx); + if (c == NULL) return REDISMODULE_OK; +- addReplyProto(c,"+",1); +- addReplyProto(c,msg,strlen(msg)); +- addReplyProto(c,"\r\n",2); ++ addReplyStatusSafe(c, msg); + return REDISMODULE_OK; + } + +diff --git a/src/networking.c b/src/networking.c +--- a/src/networking.c ++++ b/src/networking.c +@@ -627,6 +627,13 @@ void addReplyErrorSdsEx(client *c, sds err, int flags) { + sdsfree(err); + } + ++/* See addReplyErrorLength for expectations from the input string. ++ * The string is safe to contain \r and \n anywhere. */ ++void addReplyErrorSdsExSafe(client *c, sds err, int flags) { ++ err = sdsmapchars(err, "\r\n", " ", 2); ++ addReplyErrorSdsEx(c, err, flags); ++} ++ + /* See addReplyErrorLength for expectations from the input string. */ + /* As a side effect the SDS string is freed. */ + void addReplyErrorSds(client *c, sds err) { +@@ -692,11 +699,23 @@ void addReplyStatus(client *c, const char *status) { + addReplyStatusLength(c,status,strlen(status)); + } + ++/* Like addReplyStatus() but the string is safe to contain \r and \n anywhere. */ ++void addReplyStatusSafe(client *c, const char *status) { ++ sds s = sdsmapchars(sdsnew(status), "\r\n", " ", 2); ++ addReplyStatusLength(c,s,sdslen(s)); ++ sdsfree(s); ++} ++ + void addReplyStatusFormat(client *c, const char *fmt, ...) { + va_list ap; + va_start(ap,fmt); + sds s = sdscatvprintf(sdsempty(),fmt,ap); + va_end(ap); ++ /* Trim any newlines at the end (ones will be added by addReplyStatusLength) */ ++ s = sdstrim(s, "\r\n"); ++ /* Make sure there are no newlines in the middle of the string, otherwise ++ * invalid protocol is emitted. */ ++ s = sdsmapchars(s, "\r\n", " ", 2); + addReplyStatusLength(c,s,sdslen(s)); + sdsfree(s); + } +diff --git a/src/script_lua.c b/src/script_lua.c +--- a/src/script_lua.c ++++ b/src/script_lua.c +@@ -632,10 +632,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu + lua_rawget(lua,-2); + t = lua_type(lua,-1); + if (t == LUA_TSTRING) { +- sds ok = sdsnew(lua_tostring(lua,-1)); +- sdsmapchars(ok,"\r\n"," ",2); +- addReplyStatusLength(c, ok, sdslen(ok)); +- sdsfree(ok); ++ addReplyStatusSafe(c, lua_tostring(lua,-1)); + lua_pop(lua,2); + return; + } +@@ -1673,7 +1670,7 @@ void luaCallFunction(scriptRunCtx* run_ctx, lua_State *lua, robj** keys, size_t + err_info.source, + err_info.line); + } +- addReplyErrorSdsEx(c, final_msg, err_info.ignore_err_stats_update? ERR_REPLY_FLAG_NO_STATS_UPDATE : 0); ++ addReplyErrorSdsExSafe(c, final_msg, err_info.ignore_err_stats_update? ERR_REPLY_FLAG_NO_STATS_UPDATE : 0); + luaErrorInformationDiscard(&err_info); + } + lua_pop(lua,1); /* Consume the Lua error */ +diff --git a/src/server.h b/src/server.h +--- a/src/server.h ++++ b/src/server.h +@@ -2783,12 +2783,14 @@ void addReplyOrErrorObject(client *c, robj *reply); + void afterErrorReply(client *c, const char *s, size_t len, int flags); + void addReplyErrorFormatInternal(client *c, int flags, const char *fmt, va_list ap); + void addReplyErrorSdsEx(client *c, sds err, int flags); ++void addReplyErrorSdsExSafe(client *c, sds err, int flags); + void addReplyErrorSds(client *c, sds err); + void addReplyErrorSdsSafe(client *c, sds err); + void addReplyError(client *c, const char *err); + void addReplyErrorArity(client *c); + void addReplyErrorExpireTime(client *c); + void addReplyStatus(client *c, const char *status); ++void addReplyStatusSafe(client *c, const char *s); + void addReplyDouble(client *c, double d); + void addReplyBigNum(client *c, const char *num, size_t len); + void addReplyHumanLongDouble(client *c, long double d); +diff --git a/tests/modules/reply.c b/tests/modules/reply.c +--- a/tests/modules/reply.c ++++ b/tests/modules/reply.c +@@ -82,6 +82,19 @@ int rw_array(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + return REDISMODULE_OK; + } + ++int rw_simplestring_array(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { ++ if (argc < 2) return RedisModule_WrongArity(ctx); ++ ++ RedisModule_ReplyWithArray(ctx, argc - 1); ++ for (int i = 1; i < argc; ++i) { ++ size_t len; ++ const char *str = RedisModule_StringPtrLen(argv[i], &len); ++ RedisModule_ReplyWithSimpleString(ctx, str); ++ } ++ ++ return REDISMODULE_OK; ++} ++ + int rw_map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + +@@ -194,6 +207,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.array",rw_array,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; ++ if (RedisModule_CreateCommand(ctx,"rw.simplestring_array",rw_simplestring_array,"",0,0,0) != REDISMODULE_OK) ++ return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.map",rw_map,"",0,0,0) != REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"rw.attribute",rw_attribute,"",0,0,0) != REDISMODULE_OK) +diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl +--- a/tests/unit/functions.tcl ++++ b/tests/unit/functions.tcl +@@ -1020,6 +1020,15 @@ start_server {tags {"scripting"}} { + + r config set maxmemory 0 + } {OK} {needs:config-maxmemory} ++ ++ test {FUNCTION - test function list with CRLF injection attempt} { ++ r function flush ++ ++ # This test verifies that CRLF characters are properly handled ++ # to prevent protocol injection attacks when using FUNCTION LIST. ++ # The error message should have CRLF replaced with spaces. ++ assert_error {*Unknown argument X +INJECTED*} {r function list "X\r\n+INJECTED"} ++ } + } + + start_server {tags {"scripting"}} { +diff --git a/tests/unit/moduleapi/reply.tcl b/tests/unit/moduleapi/reply.tcl +--- a/tests/unit/moduleapi/reply.tcl ++++ b/tests/unit/moduleapi/reply.tcl +@@ -143,6 +143,22 @@ start_server {tags {"modules"}} { + assert_match "WRONGTYPE A type error: foo5" $e + } + ++ test "RESP$proto: redis.call reply parsing with invalid CRLF character" { ++ # When Lua parses redis.call replies, the current implementation only ++ # searches for '\r' characters without verifying that '\n' follows. If a '\r' ++ # appears in the protocol data (not as part of the CRLF delimiter), the parser ++ # incorrectly treats it as a valid '\r\n' terminator. ++ # ++ # Example: Protocol data containing "\rx=100000000000" would be parsed as: ++ # - '\rx' is treated as line terminator (should require '\r\n') ++ # - '=100000000000' is interpreted as length specifier ++ # - Lua attempts to create a massive string -> Out of Memory ++ r deferred 1 ++ r eval {return redis.call('rw.simplestring_array', '\rx=100000000000', 'hello')} 0 ++ assert_equal [r rawread 30] "*2\r\n+ x=100000000000\r\n+hello\r\n" ++ r deferred 0 ++ } ++ + r hello 2 + } + +diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl +--- a/tests/unit/scripting.tcl ++++ b/tests/unit/scripting.tcl +@@ -2385,6 +2385,14 @@ start_server {tags {"scripting"}} { + assert_equal [errorrstat ERR r] {count=1} + } + ++ test "LUA redis.error_reply API with CRLF injection attempt" { ++ catch { ++ r eval {error(redis.error_reply("X\r\n+INJECTED"))} 0 ++ } err ++ # The error message should have CRLF replaced with spaces ++ assert_match {ERR X +INJECTED*} $err ++ } ++ + test "LUA redis.status_reply API" { + r config resetstat + r readraw 1 diff -Nru redis-8.0.2/debian/patches/0013-CVE-2026-21863.patch redis-8.0.2/debian/patches/0013-CVE-2026-21863.patch --- redis-8.0.2/debian/patches/0013-CVE-2026-21863.patch 1970-01-01 00:00:00.000000000 +0000 +++ redis-8.0.2/debian/patches/0013-CVE-2026-21863.patch 2026-05-13 04:00:00.000000000 +0000 @@ -0,0 +1,189 @@ +From 416939303d2550aefff73ac180f41b84c12ba6c0 Mon Sep 17 00:00:00 2001 +From: Roshan Khatri +Date: Mon, 23 Feb 2026 18:46:43 +0000 +Subject: [PATCH] Fix for [CVE-2026-21863] Remote DoS with malformed Valkey + Cluster bus message + +Signed-off-by: Roshan Khatri +Co-authored-by: Madelyn Olson + +[Backport from Valkey upstream commit + https://github.com/valkey-io/valkey/commit/416939303d2550aefff73ac180f41b84c12ba6c0 + (no Redis-upstream commit exists for this CVE; Valkey is a fork of Redis + with closely shared cluster code).] + +[Adapted for redis-8.0.2: + - The validation lives inline in clusterProcessPacket() rather than in + a separate clusterIsValidPacket(); on a malformed packet redis-8.0.2 + returns 1 (drop packet, keep the link) instead of 0. + - The local `extlen` in this block is `uint16_t` in redis-8.0.2, not + `uint32_t`; surrounding context lines were updated accordingly. + - The serverLog() messages, the two new checks (gossip count and + extension header bounds) and the new test file + tests/unit/cluster/packet.tcl are byte-identical to upstream.] +--- + src/cluster_legacy.c | 21 +++++++ + tests/unit/cluster/packet.tcl | 113 ++++++++++++++++++++++++++++++++++ + 2 files changed, 134 insertions(+) + create mode 100644 tests/unit/cluster/packet.tcl + +diff --git a/src/cluster_legacy.c b/src/cluster_legacy.c +--- a/src/cluster_legacy.c ++++ b/src/cluster_legacy.c +@@ -2752,17 +2752,38 @@ int clusterProcessPacket(clusterLink *link) { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += (sizeof(clusterMsgDataGossip)*count); + ++ /* Make sure that the number of gossip messages fit in the remaining ++ * space in the message. */ ++ if (totlen < explen) { ++ serverLog(LL_WARNING, ++ "Received invalid %s packet with gossip count %d that exceeds " ++ "total packet length (%lld)", ++ clusterGetMessageTypeString(type), count, (unsigned long long)totlen); ++ return 1; ++ } ++ + /* If there is extension data, which doesn't have a fixed length, + * loop through them and validate the length of it now. */ + if (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA) { + clusterMsgPingExt *ext = getInitialPingExt(hdr, count); + while (extensions--) { ++ /* Make sure there is at least enough memory for the extension information so ++ * we can parse it. */ ++ if ((totlen - explen) < sizeof(clusterMsgPingExt)) { ++ serverLog(LL_WARNING, ++ "Received invalid %s packet with extension data that exceeds " ++ "total packet length (%lld)", ++ clusterGetMessageTypeString(type), (unsigned long long)totlen); ++ return 1; ++ } + uint16_t extlen = getPingExtLength(ext); + if (extlen % 8 != 0) { + serverLog(LL_WARNING, "Received a %s packet without proper padding (%d bytes)", + clusterGetMessageTypeString(type), (int) extlen); + return 1; + } ++ /* Similar check to earlier, but we want to make sure the extension length is valid ++ * this time. */ + if ((totlen - explen) < extlen) { + serverLog(LL_WARNING, "Received invalid %s packet with extension data that exceeds " + "total packet length (%lld)", clusterGetMessageTypeString(type), +diff --git a/tests/unit/cluster/packet.tcl b/tests/unit/cluster/packet.tcl +new file mode 100644 +--- /dev/null ++++ b/tests/unit/cluster/packet.tcl +@@ -0,0 +1,113 @@ ++# Test that cluster bus messages with certain invalid packets are rejected ++# and don't crash the system. ++proc create_cluster_meet_packet {sender_name sender_port sender_cport} { ++ # Constants ++ set CLUSTER_NAMELEN 40 ++ set CLUSTER_SLOTS 16384 ++ set NET_IP_STR_LEN 46 ++ set CLUSTERMSG_TYPE_MEET 2 ++ ++ # Build the packet ++ set packet "" ++ ++ # Signature "RCmb" (4 bytes) ++ append packet "RCmb" ++ ++ # totlen (uint32_t) - will be updated at the end ++ append packet [binary format I 0] ++ ++ # ver (uint16_t) - protocol version 1 ++ append packet [binary format S 1] ++ ++ # port (uint16_t) ++ append packet [binary format S $sender_port] ++ ++ # type (uint16_t) - MEET ++ append packet [binary format S $CLUSTERMSG_TYPE_MEET] ++ ++ # count (uint16_t) - 100 gossip messages ++ # Value intentionally set to a high value, even though no gossip ++ # messages are included. ++ append packet [binary format S 100] ++ ++ # currentEpoch (uint64_t) ++ append packet [binary format W 1] ++ ++ # configEpoch (uint64_t) ++ append packet [binary format W 1] ++ ++ # offset (uint64_t) ++ append packet [binary format W 0] ++ ++ # sender[40] - node name ++ set sender_padded [string range "${sender_name}[string repeat "\x00" $CLUSTER_NAMELEN]" 0 [expr {$CLUSTER_NAMELEN - 1}]] ++ append packet $sender_padded ++ ++ # myslots[2048] - all zeros ++ append packet [string repeat "\x00" [expr {$CLUSTER_SLOTS / 8}]] ++ ++ # replicaof[40] - all zeros ++ append packet [string repeat "\x00" $CLUSTER_NAMELEN] ++ ++ # myip[46] - all zeros ++ append packet [string repeat "\x00" $NET_IP_STR_LEN] ++ ++ # extensions (uint16_t) - Set to 2 ++ append packet [binary format S 2000000000] ++ ++ # notused1[30] - reserved ++ append packet [string repeat "\x00" 30] ++ ++ # pport (uint16_t) ++ append packet [binary format S 0] ++ ++ # cport (uint16_t) - cluster bus port ++ append packet [binary format S $sender_cport] ++ ++ # flags (uint16_t) - CLUSTER_NODE_PRIMARY ++ append packet [binary format S 1] ++ ++ # state (unsigned char) - CLUSTER_OK ++ append packet [binary format c 0] ++ ++ # mflags[3] - message flags (WITH CLUSTERMSG_FLAG0_EXT_DATA flag set) ++ # CLUSTERMSG_FLAG0_EXT_DATA = (1 << 2) = 4 ++ append packet [binary format ccc 4 0 0] ++ ++ # Update totlen ++ set totlen [string length $packet] ++ set packet [string replace $packet 4 7 [binary format I $totlen]] ++ ++ return $packet ++} ++ ++start_cluster 1 0 {tags {external:skip cluster tls:skip}} { ++ test "Packet with missing gossip messages don't cause invalid read" { ++ set base_port [srv 0 port] ++ set cluster_port [expr {$base_port + 10000}] ++ set fake_node_id "abcdef1234567890abcdef1234567890abcdef12" ++ ++ # Get initial total messages received ++ set info_before [R 0 cluster info] ++ regexp {cluster_stats_messages_received:(\d+)} $info_before -> initial_received ++ ++ # Create a packet with extensions=0 but CLUSTERMSG_FLAG0_EXT_DATA flag set ++ set packet [create_cluster_meet_packet $fake_node_id $base_port $cluster_port] ++ ++ # Verify packet length ++ set packet_len [string length $packet] ++ ++ # Send the packet after configuring the socket to accept binary data ++ set sock [socket 127.0.0.1 $cluster_port] ++ fconfigure $sock -translation binary -encoding binary -buffering none -blocking 1 ++ puts -nonewline $sock $packet ++ flush $sock ++ close $sock ++ ++ wait_for_condition 1000 10 { ++ [CI 0 cluster_stats_messages_received] == 1 ++ } else { ++ fail "Packet was never received" ++ } ++ } ++} diff -Nru redis-8.0.2/debian/patches/series redis-8.0.2/debian/patches/series --- redis-8.0.2/debian/patches/series 2025-10-07 18:00:35.000000000 +0000 +++ redis-8.0.2/debian/patches/series 2026-05-13 04:00:00.000000000 +0000 @@ -10,3 +10,5 @@ 0009-CVE-2025-46819.patch 0010-CVE-2025-46818.patch 0011-CVE-2025-46817.patch +0012-CVE-2025-67733.patch +0013-CVE-2026-21863.patch