Hi Salvatore, hi Lucas,

On 2026-03-21 18:26:15, Peter Wienemann wrote:
Hi Lucas, hi Salvatore,

On 2026-03-15 21:01:46, Salvatore Bonaccorso wrote:
The following vulnerabilities were published for valkey.

CVE-2025-67733[0]:
| Valkey is a distributed key-value database. Prior to versions 9.0.2,
| 8.1.6, 8.0.7, and 7.2.12, a malicious user can use scripting
| commands to inject arbitrary information into the response stream
| for the given client, potentially corrupting or returning tampered
| data to other users on the same connection. The error handling code
| for lua scripts does not properly handle null characters. Versions
| 9.0.2, 8.1.6, 8.0.7, and 7.2.12 fix the issue.


CVE-2026-21863[1]:
| Valkey is a distributed key-value database. Prior to versions 9.0.2,
| 8.1.6, 8.0.7, and 7.2.12, a malicious actor with access to the
| Valkey clusterbus port can send an invalid packet that may cause an
| out bound read, which might result in the system crashing. The
| Valkey clusterbus packet processing code does not validate that a
| clusterbus ping extension packet is located within buffer of the
| clusterbus packet before attempting to read it. Versions 9.0.2,
| 8.1.6, 8.0.7, and 7.2.12 fix the issue. As an additional mitigation,
| don't expose the cluster bus connection directly to end users, and
| protect the connection with its own network ACLs.

what are your plans concerning the above vulnerabilities?

If you need a helping hand, I can prepare a debdiff for trixie.

I went ahead and prepared a debdiff assuming you have a fix via trixie-security in mind. Just let me know if you prefer to go via trixie-pu.

Lucas, feel free to intervene if you prefer to take care of it yourself.

The upstream patch for CVE-2026-21863 had to be backported. The difference between the backport and the upstream version is due to this change introduced by version 8.1.2:

https://github.com/valkey-io/valkey/commit/0a3186ae1e338701ae1201c8dc08e4a463a5b647

So the difference is only in the neighbouring code.

I checked that the CVE-2025-67733 exploit in [0] worked prior to the fix and that it stopped working after applying the attached changes.

Lucas, I prepared a debian/trixie branch which contains the 8.1.1+dfsg1-3+deb13u1 changes introduced by Moritz some time ago and the changes included in the attached debdiff. If you want me to push it to salsa, just let me know.

Best regards

Peter

[0] https://github.com/JYlab/CVE-2025-67733
diff -Nru valkey-8.1.1+dfsg1/debian/changelog 
valkey-8.1.1+dfsg1/debian/changelog
--- valkey-8.1.1+dfsg1/debian/changelog 2025-10-07 21:33:04.000000000 +0200
+++ valkey-8.1.1+dfsg1/debian/changelog 2026-03-29 10:34:29.000000000 +0200
@@ -1,3 +1,12 @@
+valkey (8.1.1+dfsg1-3+deb13u2) trixie-security; urgency=medium
+
+  * Non-maintainer upload.
+  * Fix the following vulnerabilities (Closes: #1130911)
+    - CVE-2025-67733: RESP Protocol Injection via Lua error_reply
+    - CVE-2026-21863: Remote DoS with malformed Valkey Cluster bus message
+
+ -- Peter Wienemann <[email protected]>  Sun, 29 Mar 2026 10:34:29 +0200
+
 valkey (8.1.1+dfsg1-3+deb13u1) trixie-security; urgency=medium
 
   * (CVE-2025-49844) A Lua script may lead to remote code execution
diff -Nru valkey-8.1.1+dfsg1/debian/patches/CVE-2025-67733.patch 
valkey-8.1.1+dfsg1/debian/patches/CVE-2025-67733.patch
--- valkey-8.1.1+dfsg1/debian/patches/CVE-2025-67733.patch      1970-01-01 
01:00:00.000000000 +0100
+++ valkey-8.1.1+dfsg1/debian/patches/CVE-2025-67733.patch      2026-03-29 
10:34:29.000000000 +0200
@@ -0,0 +1,92 @@
+Author: Roshan Khatri <[email protected]>, Madelyn Olson 
<[email protected]>
+Date: Mon, 23 Feb 2026 18:46:43 +0000
+Description: Fix for [CVE-2025-67733] RESP Protocol Injection via Lua 
error_reply
+
+Origin: upstream, 
https://github.com/valkey-io/valkey/commit/3d7598e8c7db4857a0e76582861dec14b555c343.diff
+Bug-Debian: https://bugs.debian.org/1130911
+---
+ src/lua/script_lua.c     |  2 +-
+ src/networking.c         | 14 ++++++++++++++
+ src/server.h             |  1 +
+ tests/unit/scripting.tcl | 18 ++++++++++++++++++
+ 4 files changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/src/lua/script_lua.c b/src/lua/script_lua.c
+index eb42c5e..4211f93 100644
+--- a/src/lua/script_lua.c
++++ b/src/lua/script_lua.c
+@@ -1853,7 +1853,7 @@ void luaCallFunction(scriptRunCtx *run_ctx,
+                 final_msg =
+                     sdscatfmt(final_msg, " script: %s, on %s:%s.", 
run_ctx->funcname, 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/networking.c b/src/networking.c
+index 0b54308..7c1f25e 100644
+--- a/src/networking.c
++++ b/src/networking.c
+@@ -701,6 +701,20 @@ void addReplyError(client *c, const char *err) {
+ }
+ 
+ /* Add error reply to the given client.
++ * Supported flags:
++ * ERR_REPLY_FLAG_NO_STATS_UPDATE - indicate not to perform any error stats 
updates
++ * As a side effect the SDS string is freed. */
++void addReplyErrorSdsExSafe(client *c, sds err, int flags) {
++    /* Trim any newlines at the end (ones will be added by 
addReplyErrorLength) */
++    err = sdstrim(err, "\r\n");
++    /* Make sure there are no newlines in the middle of the string, otherwise
++     * invalid protocol is emitted. */
++    err = sdsmapchars(err, "\r\n", "  ", 2);
++    addReplyErrorSdsEx(c, err, flags);
++}
++
++/* Add error reply to the given client.
++ * See addReplyErrorLength for expectations from the input string.
+  * Supported flags:
+  * * ERR_REPLY_FLAG_NO_STATS_UPDATE - indicate not to perform any error stats 
updates */
+ void addReplyErrorSdsEx(client *c, sds err, int flags) {
+diff --git a/src/server.h b/src/server.h
+index 222fd1f..77d895a 100644
+--- a/src/server.h
++++ b/src/server.h
+@@ -2692,6 +2692,7 @@ 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);
+diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl
+index e2e59f6..64e0649 100644
+--- a/tests/unit/scripting.tcl
++++ b/tests/unit/scripting.tcl
+@@ -2523,6 +2523,24 @@ start_server {tags {"scripting"}} {
+         assert_equal [errorrstat MY_ERR_CODE r] {} ;# error stats were not 
incremented
+     }
+ 
++    test "LUA redis.error_reply API sanitation" {
++        r config resetstat
++        assert_error {ERR*} {
++            r eval {error(redis.error_reply("-ERR\r\n-ERR FAKE"))} 0
++        }
++        assert_equal PONG [r ping]
++        assert_equal [errorrstat ERR r] {count=1}
++    }
++
++    test "LUA error function API sanitation" {
++        r config resetstat
++        assert_error {ERR*} {
++            r eval {error("-ERR\r\n-ERR FAKE")} 0
++        }
++        assert_equal PONG [r ping]
++        assert_equal [errorrstat ERR r] {count=1}
++    }
++
+     test "LUA test pcall" {
+         assert_equal [
+             r eval {local status, res = pcall(function() return 1 end); 
return 'status: ' .. tostring(status) .. ' result: ' .. res} 0
diff -Nru valkey-8.1.1+dfsg1/debian/patches/CVE-2026-21863.patch 
valkey-8.1.1+dfsg1/debian/patches/CVE-2026-21863.patch
--- valkey-8.1.1+dfsg1/debian/patches/CVE-2026-21863.patch      1970-01-01 
01:00:00.000000000 +0100
+++ valkey-8.1.1+dfsg1/debian/patches/CVE-2026-21863.patch      2026-03-29 
10:34:29.000000000 +0200
@@ -0,0 +1,174 @@
+Author: Roshan Khatri <[email protected]>, Madelyn Olson 
<[email protected]>
+Date: Mon, 23 Feb 2026 18:46:43 +0000
+Description: Fix for [CVE-2026-21863] Remote DoS with malformed Valkey Cluster 
bus message
+
+Origin: backport, 
https://github.com/valkey-io/valkey/commit/416939303d2550aefff73ac180f41b84c12ba6c0.diff
+Bug-Debian: https://bugs.debian.org/1130911
+---
+ 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
+index dfb0e7b..eef9cae 100644
+--- a/src/cluster_legacy.c
++++ b/src/cluster_legacy.c
+@@ -3132,17 +3132,38 @@ int clusterIsValidPacket(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 0;
++        }
++
+         /* 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 0;
++                }
+                 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 0;
+                 }
++                /* 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 "
+diff --git a/tests/unit/cluster/packet.tcl b/tests/unit/cluster/packet.tcl
+new file mode 100644
+index 0000000..1b199f0
+--- /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 valkey-8.1.1+dfsg1/debian/patches/series 
valkey-8.1.1+dfsg1/debian/patches/series
--- valkey-8.1.1+dfsg1/debian/patches/series    2025-10-07 21:33:04.000000000 
+0200
+++ valkey-8.1.1+dfsg1/debian/patches/series    2026-03-29 10:34:29.000000000 
+0200
@@ -8,3 +8,5 @@
 CVE-2025-32023.patch
 CVE-2025-48367.patch
 CVE-2025-49844_CVE-2025-46817_CVE-2025-46818_CVE-2025-46819.patch
+CVE-2025-67733.patch
+CVE-2026-21863.patch

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to