Control: tags -1 + patch Hi Maintainers,
In Debian forky 5:8.0.6-3, the 0015-CVE-2026-92925.patch patch file adding `tests/unit/cluster/hostnames.tcl` calls `build_cluster_bus_header`, which was missing from `tests/support/cluster_util.tcl`. This causes the test "PING with hostname extension missing null terminator is rejected" to fail. I have updated the patch to include the missing `build_cluster_bus_header` procedure in `tests/support/cluster_util.tcl`. Attached is the updated patch. Thanks! Hurman
From: Sergei Georgiev <[email protected]> Date: Fri, 5 Jun 2026 11:56:05 +0300 Subject: [PATCH] Reject cluster bus PING extensions with missing null terminator (#15263) The cluster bus PING/PONG/MEET packet parser validated extension padding and total length but never checked that string-carrying extensions are properly null-terminated, allowing a crafted packet to trigger out-of-bounds reads when the payload is later consumed as a C string. 1. **Null-termination check for hostname and human-nodename extensions (`cluster_legacy.c`)** Added a check inside the existing extension-validation loop in `clusterProcessPacket`: for `CLUSTERMSG_EXT_TYPE_HOSTNAME` and `CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME` extension types, it verifies that the data portion is non-empty (`datalen > 0`) and that the last byte is `''`. Packets failing this check are rejected with a warning log and an early return, the same way other malformed-extension cases are handled. 2. **Test (`hostnames.tcl`)** A new test exercises the rejection path by constructing a raw cluster-bus PING packet with a 32-byte hostname extension that contains no `''`, sending it directly to a node's bus port, and verifying the packet is dropped (warning logged, hostname not updated in `CLUSTER NODES`). Two helper procs (`build_cluster_bus_ping` and `build_hostname_extension`) build the binary packet from scratch in Tcl, allowing fine-grained control over extension contents without needing a modified Redis sender. Origin: upstream, https://github.com/redis/redis/commit/37894faeea11e2db28b9fc2af378a762d2c36523.patch --- src/cluster_legacy.c | 40 +++++++++++++++++++- tests/support/cluster_util.tcl | 34 +++++++++++++++++ tests/unit/cluster/hostnames.tcl | 78 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) Index: redis-8.0.6/src/cluster_legacy.c =================================================================== --- redis-8.0.6.orig/src/cluster_legacy.c +++ redis-8.0.6/src/cluster_legacy.c @@ -220,7 +220,7 @@ sds auxShardIdGetter(clusterNode *n, sds } int auxShardIdPresent(clusterNode *n) { - return strlen(n->shard_id); + return strnlen(n->shard_id, CLUSTER_NAMELEN); } int auxHumanNodenameSetter(clusterNode *n, void *value, int length) { @@ -2782,7 +2782,7 @@ int clusterProcessPacket(clusterLink *li return 1; } uint16_t extlen = getPingExtLength(ext); - if (extlen % 8 != 0) { + if (extlen < sizeof(clusterMsgPingExt) || extlen % 8 != 0) { serverLog(LL_WARNING, "Received a %s packet without proper padding (%d bytes)", clusterGetMessageTypeString(type), (int) extlen); return 1; @@ -2795,6 +2795,42 @@ int clusterProcessPacket(clusterLink *li (unsigned long long) totlen); return 1; } + uint16_t exttype = ntohs(ext->type); + uint32_t datalen = extlen - sizeof(clusterMsgPingExt); + if (exttype == CLUSTERMSG_EXT_TYPE_HOSTNAME || + exttype == CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME) { + char *str = (char *) ext->ext; + if (datalen == 0 || str[datalen - 1] != '\0') { + serverLog(LL_WARNING, + "Received %s packet with missing null terminator in extension type %d", + clusterGetMessageTypeString(type), exttype); + return 1; + } + } else if (exttype == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) { + if (datalen < sizeof(clusterMsgPingExtForgottenNode)) { + serverLog(LL_WARNING, + "Received %s packet with truncated extension type %d", + clusterGetMessageTypeString(type), exttype); + return 1; + } + } else if (exttype == CLUSTERMSG_EXT_TYPE_SHARDID) { + char *str = (char *) ext->ext; + if (datalen < sizeof(clusterMsgPingExtShardId) || + verifyClusterNodeId(str, CLUSTER_NAMELEN) != C_OK) + { + serverLog(LL_WARNING, + "Received %s packet with invalid shard id in extension type %d", + clusterGetMessageTypeString(type), exttype); + return 1; + } + } else if (exttype == CLUSTERMSG_EXT_TYPE_INTERNALSECRET) { + if (datalen < sizeof(clusterMsgPingExtInternalSecret)) { + serverLog(LL_WARNING, + "Received %s packet with truncated extension type %d", + clusterGetMessageTypeString(type), exttype); + return 1; + } + } explen += extlen; ext = getNextPingExt(ext); } Index: redis-8.0.6/tests/unit/cluster/hostnames.tcl =================================================================== --- redis-8.0.6.orig/tests/unit/cluster/hostnames.tcl +++ redis-8.0.6/tests/unit/cluster/hostnames.tcl @@ -16,6 +16,36 @@ proc get_slot_field {slot_output shard_i return [lindex [lindex [lindex $slot_output $shard_id] $node_id] $attrib_id] } +proc build_cluster_bus_ping {sender_name sender_port sender_cport extensions_data} { + set CLUSTERMSG_TYPE_PING 0 + set CLUSTERMSG_FLAG0_EXT_DATA 0x04 + set CLUSTER_NODE_MASTER 1 + + set num_extensions [expr {[string length $extensions_data] > 0 ? 1 : 0}] + set mflags0 [expr {$num_extensions > 0 ? $CLUSTERMSG_FLAG0_EXT_DATA : 0}] + set totlen [expr {2256 + [string length $extensions_data]}] + + set hdr [build_cluster_bus_header $sender_name $sender_port $sender_cport \ + $CLUSTERMSG_TYPE_PING $totlen $num_extensions $CLUSTER_NODE_MASTER $mflags0] + append hdr $extensions_data + return $hdr +} + +proc build_hostname_extension {hostname_bytes} { + set ext_header_size 8 + set total_ext_len [expr {$ext_header_size + [string length $hostname_bytes]}] + set padded_len [expr {(($total_ext_len + 7) / 8) * 8}] + set padding_len [expr {$padded_len - $total_ext_len}] + + set ext "" + append ext [binary format I $padded_len] + append ext [binary format S 0] + append ext [binary format S 0] + append ext $hostname_bytes + append ext [string repeat \x00 $padding_len] + return $ext +} + # Start a cluster with 3 masters and 4 replicas. # These tests rely on specific node ordering, so make sure no node fails over. start_cluster 3 4 {tags {external:skip cluster} overrides {cluster-replica-no-failover yes}} { @@ -227,4 +257,52 @@ test "Test hostname validation" { # Note this isn't a valid hostname, but it passes our internal validation R 0 config set cluster-announce-hostname "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-." } + +test "PING with hostname extension missing null terminator is rejected" { + for {set j 0} {$j < [llength $::servers]} {incr j} { + R $j config set cluster-announce-hostname "" + } + wait_for_condition 50 100 { + [are_hostnames_propagated ""] eq 1 + } else { + fail "hostnames were not cleared" + } + + set node1_id [R 1 CLUSTER MYID] + set target_host [srv 0 host] + set target_bus_port [expr {[srv 0 port] + 10000}] + set sender_port [srv -1 port] + set sender_cport [expr {$sender_port + 10000}] + + # Freeze node 1 so its real PINGs cannot overwrite the injected hostname. + set node1_pid [srv -1 pid] + pause_process $node1_pid + + set payload_len 32 + set bad_hostname [string repeat A $payload_len] + set bad_ext [build_hostname_extension $bad_hostname] + set bad_packet [build_cluster_bus_ping $node1_id $sender_port $sender_cport $bad_ext] + + # Record the log position before injecting the bad packet. + set loglines [count_log_lines 0] + + if {$::tls} { + set fd [::tls::socket \ + -cafile "$::tlsdir/ca.crt" \ + -certfile "$::tlsdir/client.crt" \ + -keyfile "$::tlsdir/client.key" \ + $target_host $target_bus_port] + } else { + set fd [socket $target_host $target_bus_port] + } + fconfigure $fd -translation binary -buffering full + puts -nonewline $fd $bad_packet + flush $fd + + # Verify the server logged the proper rejection message. + wait_for_log_messages 0 {"*missing null terminator*"} $loglines 50 100 + + close $fd + resume_process $node1_pid +} } Index: redis-8.0.6/tests/support/cluster_util.tcl =================================================================== --- redis-8.0.6.orig/tests/support/cluster_util.tcl +++ redis-8.0.6/tests/support/cluster_util.tcl @@ -227,3 +227,37 @@ proc check_cluster_node_mark {flag ref_n } fail "Unable to find instance id in cluster nodes. ID: $instance_id_to_check" } + +proc build_cluster_bus_header {sender_name sender_port sender_cport msg_type totlen {num_extensions 0} {flags 0} {mflags0 0}} { + set CLUSTER_NAMELEN 40 + set NET_IP_STR_LEN 46 + + set sender_padded [binary format a${CLUSTER_NAMELEN} $sender_name] + set myslots [string repeat \x00 [expr {16384/8}]] + set slaveof [string repeat \x00 $CLUSTER_NAMELEN] + set myip [string repeat \x00 $NET_IP_STR_LEN] + set notused1 [string repeat \x00 30] + + set hdr "" + append hdr "RCmb" + append hdr [binary format I $totlen] + append hdr [binary format S 1] ;# ver + append hdr [binary format S $sender_port] ;# port + append hdr [binary format S $msg_type] ;# type + append hdr [binary format S 0] ;# count + append hdr [binary format W 1] ;# currentEpoch + append hdr [binary format W 2] ;# configEpoch + append hdr [binary format W 0] ;# offset + append hdr $sender_padded ;# sender + append hdr $myslots ;# myslots + append hdr $slaveof ;# slaveof + append hdr $myip ;# myip + append hdr [binary format S $num_extensions] ;# extensions + append hdr $notused1 ;# notused1 + append hdr [binary format S 0] ;# pport + append hdr [binary format S $sender_cport] ;# cport + append hdr [binary format S $flags] ;# flags + append hdr [binary format c 0] ;# state + append hdr [binary format ccc $mflags0 0 0] ;# mflags + return $hdr +} \ No newline at end of file

