mirror of
https://github.com/redis/go-redis.git
synced 2026-10-22 19:49:00 +03:00
fix(command): reject odd-length flat replies in Z/KeyValue parsers (#3900)
* fix(command): reject odd-length flat replies in Z/KeyValue parsers * ci: retrigger govulncheck, setup-go now resolves 1.26.x to go1.26.5
This commit is contained in:
@@ -2030,6 +2030,9 @@ func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
||||
if array {
|
||||
cmd.val = make([]KeyValue, n)
|
||||
} else {
|
||||
if n%2 != 0 {
|
||||
return fmt.Errorf("redis: got %d elements in the key-value array, wanted a multiple of 2", n)
|
||||
}
|
||||
cmd.val = make([]KeyValue, n/2)
|
||||
}
|
||||
|
||||
@@ -4137,6 +4140,9 @@ func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
||||
if array {
|
||||
cmd.val = make([]Z, n)
|
||||
} else {
|
||||
if n%2 != 0 {
|
||||
return fmt.Errorf("redis: got %d elements in the sorted set array, wanted a multiple of 2", n)
|
||||
}
|
||||
cmd.val = make([]Z, n/2)
|
||||
}
|
||||
|
||||
@@ -6235,6 +6241,9 @@ func (cmd *ZSliceWithKeyCmd) readReply(rd *proto.Reader) (err error) {
|
||||
if array {
|
||||
cmd.val = make([]Z, n)
|
||||
} else {
|
||||
if n%2 != 0 {
|
||||
return fmt.Errorf("redis: got %d elements in the sorted set array, wanted a multiple of 2", n)
|
||||
}
|
||||
cmd.val = make([]Z, n/2)
|
||||
}
|
||||
|
||||
|
||||
62
command_flat_reply_test.go
Normal file
62
command_flat_reply_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// A RESP2 flat reply (a single array of alternating member/score or key/value
|
||||
// elements) must have an even length. An odd length means the parser would read
|
||||
// n/2 pairs and leave the trailing element on the wire, desyncing the pooled
|
||||
// connection. These parsers must reject it, matching the guard the sibling flat
|
||||
// parsers (VLINKS, VSIM) already have.
|
||||
func TestFlatReplyOddLengthRejected(t *testing.T) {
|
||||
t.Run("ZSliceCmd", func(t *testing.T) {
|
||||
rd := proto.NewReader(strings.NewReader("*3\r\n$1\r\na\r\n$3\r\n1.5\r\n$1\r\nb\r\n"))
|
||||
cmd := NewZSliceCmd(context.Background())
|
||||
if err := cmd.readReply(rd); err == nil {
|
||||
t.Fatalf("odd-length flat reply accepted; readReply returned nil (val=%+v)", cmd.val)
|
||||
}
|
||||
})
|
||||
t.Run("KeyValueSliceCmd", func(t *testing.T) {
|
||||
rd := proto.NewReader(strings.NewReader("*3\r\n$1\r\nk\r\n$1\r\nv\r\n$1\r\nx\r\n"))
|
||||
cmd := NewKeyValueSliceCmd(context.Background())
|
||||
if err := cmd.readReply(rd); err == nil {
|
||||
t.Fatalf("odd-length flat reply accepted; readReply returned nil (val=%+v)", cmd.val)
|
||||
}
|
||||
})
|
||||
t.Run("ZSliceWithKeyCmd", func(t *testing.T) {
|
||||
rd := proto.NewReader(strings.NewReader("*2\r\n$3\r\nzst\r\n*3\r\n$1\r\na\r\n$3\r\n1.5\r\n$1\r\nb\r\n"))
|
||||
cmd := NewZSliceWithKeyCmd(context.Background())
|
||||
if err := cmd.readReply(rd); err == nil {
|
||||
t.Fatalf("odd-length flat reply accepted; readReply returned nil (val=%+v)", cmd.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Even-length flat replies and the nested-array form must still parse cleanly.
|
||||
func TestFlatReplyValidStillParses(t *testing.T) {
|
||||
t.Run("flat", func(t *testing.T) {
|
||||
rd := proto.NewReader(strings.NewReader("*4\r\n$1\r\na\r\n$3\r\n1.5\r\n$1\r\nb\r\n$3\r\n2.5\r\n"))
|
||||
cmd := NewZSliceCmd(context.Background())
|
||||
if err := cmd.readReply(rd); err != nil {
|
||||
t.Fatalf("valid flat reply: %v", err)
|
||||
}
|
||||
if len(cmd.val) != 2 || cmd.val[0].Member != "a" || cmd.val[1].Member != "b" {
|
||||
t.Fatalf("unexpected val: %+v", cmd.val)
|
||||
}
|
||||
})
|
||||
t.Run("nested", func(t *testing.T) {
|
||||
rd := proto.NewReader(strings.NewReader("*2\r\n*2\r\n$1\r\na\r\n$3\r\n1.5\r\n*2\r\n$1\r\nb\r\n$3\r\n2.5\r\n"))
|
||||
cmd := NewZSliceCmd(context.Background())
|
||||
if err := cmd.readReply(rd); err != nil {
|
||||
t.Fatalf("valid nested reply: %v", err)
|
||||
}
|
||||
if len(cmd.val) != 2 {
|
||||
t.Fatalf("unexpected val: %+v", cmd.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user