Medium severity4.9NVD Advisory· Published May 8, 2024· Updated Apr 15, 2026
CVE-2024-32886
CVE-2024-32886
Description
Vitess is a database clustering system for horizontal scaling of MySQL. When executing the following simple query, the vtgate will go into an endless loop that also keeps consuming memory and eventually will run out of memory. This vulnerability is fixed in 19.0.4, 18.0.5, and 17.0.7.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/vitessio/vitessGo | >= 19.0.0, < 19.0.4 | 19.0.4 |
github.com/vitessio/vitessGo | >= 18.0.0, < 18.0.5 | 18.0.5 |
github.com/vitessio/vitessGo | < 17.0.7 | 17.0.7 |
vitess.io/vitessGo | < 0.17.7 | 0.17.7 |
vitess.io/vitessGo | >= 0.18.0, < 0.18.5 | 0.18.5 |
vitess.io/vitessGo | >= 0.19.0, < 0.19.4 | 0.19.4 |
Patches
4c46dc5b6a432Merge pull request from GHSA-649x-hxfx-57j2
8 files changed · +68 −12
go/mysql/collations/charset/convert.go+1 −1 modified@@ -70,7 +70,7 @@ func convertSlow(dst []byte, dstCharset Charset, src []byte, srcCharset Charset) for len(src) > 0 { cp, width := srcCharset.DecodeRune(src) - if cp == utf8.RuneError && width < 3 { + if cp == utf8.RuneError { failed++ cp = '?' }
go/mysql/collations/charset/helpers.go+1 −1 modified@@ -41,7 +41,7 @@ func Validate(charset Charset, input []byte) bool { } for len(input) > 0 { r, size := charset.DecodeRune(input) - if r == RuneError && size < 2 { + if r == RuneError { return false } input = input[size:]
go/mysql/collations/charset/unicode/utf16.go+3 −3 modified@@ -67,7 +67,7 @@ func (Charset_utf16be) EncodeRune(dst []byte, r rune) int { func (Charset_utf16be) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[1]) | uint16(b[0])<<8 @@ -129,7 +129,7 @@ func (Charset_utf16le) EncodeRune(dst []byte, r rune) int { func (Charset_utf16le) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[0]) | uint16(b[1])<<8 @@ -185,7 +185,7 @@ func (Charset_ucs2) EncodeRune(dst []byte, r rune) int { func (Charset_ucs2) DecodeRune(p []byte) (rune, int) { if len(p) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return rune(p[0])<<8 | rune(p[1]), 2 }
go/mysql/collations/charset/unicode/utf32.go+1 −1 modified@@ -49,7 +49,7 @@ func (Charset_utf32) EncodeRune(dst []byte, r rune) int { func (Charset_utf32) DecodeRune(p []byte) (rune, int) { if len(p) < 4 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return (rune(p[0]) << 24) | (rune(p[1]) << 16) | (rune(p[2]) << 8) | rune(p[3]), 4 }
go/vt/vtgate/evalengine/compiler_asm.go+2 −1 modified@@ -4253,7 +4253,8 @@ func (asm *assembler) Fn_UUID_TO_BIN1() { func (asm *assembler) Introduce(offset int, t sqltypes.Type, col collations.TypedCollation) { asm.emit(func(env *ExpressionEnv) int { - arg := evalToBinary(env.vm.stack[env.vm.sp-offset]) + var arg *evalBytes + arg, env.vm.err = introducerCast(env.vm.stack[env.vm.sp-offset], col.Collation) arg.tt = int16(t) arg.col = col env.vm.stack[env.vm.sp-offset] = arg
go/vt/vtgate/evalengine/compiler_test.go+24 −0 modified@@ -492,6 +492,30 @@ func TestCompilerSingle(t *testing.T) { expression: `week('2024-12-31', 5)`, result: `INT64(53)`, }, + { + expression: `convert(0xFF using utf16)`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `_utf16 0xFF`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `convert(0xFF using utf32)`, + result: `NULL`, + }, + { + expression: `cast(_utf32 0xFF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x00FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x0000FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, } for _, tc := range testCases {
go/vt/vtgate/evalengine/expr_collate.go+35 −4 modified@@ -18,6 +18,7 @@ package evalengine import ( "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/mysql/collations/charset" "vitess.io/vitess/go/sqltypes" querypb "vitess.io/vitess/go/vt/proto/query" vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" @@ -210,15 +211,45 @@ func (ca *collationAggregation) result() collations.TypedCollation { var _ Expr = (*IntroducerExpr)(nil) +func introducerCast(e eval, col collations.ID) (*evalBytes, error) { + if col == collations.CollationBinaryID { + return evalToBinary(e), nil + } + + var bytes []byte + if b, ok := e.(*evalBytes); !ok { + bytes = b.ToRawBytes() + } else { + cs := col.Get().Charset() + bytes = b.bytes + // We only need to pad here for encodings that have a minimum + // character byte width larger than 1, which is all UTF-16 + // variations and UTF-32. + switch cs.(type) { + case charset.Charset_utf16, charset.Charset_utf16le, charset.Charset_ucs2: + if len(bytes)%2 != 0 { + bytes = append([]byte{0}, bytes...) + } + case charset.Charset_utf32: + if mod := len(bytes) % 4; mod != 0 { + bytes = append(make([]byte, 4-mod), bytes...) + } + } + } + typedcol := collations.TypedCollation{ + Collation: col, + Coercibility: collations.CoerceCoercible, + Repertoire: collations.RepertoireASCII, + } + return newEvalText(bytes, typedcol), nil +} + func (expr *IntroducerExpr) eval(env *ExpressionEnv) (eval, error) { e, err := expr.Inner.eval(env) if err != nil { return nil, err } - if expr.TypedCollation.Collation == collations.CollationBinaryID { - return evalToBinary(e), nil - } - return evalToVarchar(e, expr.TypedCollation.Collation, false) + return introducerCast(e, expr.TypedCollation.Collation) } func (expr *IntroducerExpr) typeof(env *ExpressionEnv, fields []*querypb.Field) (sqltypes.Type, typeFlag) {
go/vt/vtgate/evalengine/translate.go+1 −1 modified@@ -361,7 +361,7 @@ func (ast *astCompiler) translateIntroducerExpr(introduced *sqlparser.Introducer case collations.CollationBinaryID: lit.inner = evalToBinary(lit.inner) default: - lit.inner, err = evalToVarchar(lit.inner, collation, false) + lit.inner, err = introducerCast(lit.inner, collation) if err != nil { return nil, err }
d438adf7e34aMerge pull request from GHSA-649x-hxfx-57j2
8 files changed · +71 −16
go/mysql/collations/charset/convert.go+1 −1 modified@@ -72,7 +72,7 @@ func convertSlow(dst []byte, dstCharset Charset, src []byte, srcCharset Charset) for len(src) > 0 { cp, width := srcCharset.DecodeRune(src) - if cp == utf8.RuneError && width < 3 { + if cp == utf8.RuneError { failed++ cp = '?' }
go/mysql/collations/charset/helpers.go+1 −1 modified@@ -41,7 +41,7 @@ func Validate(charset Charset, input []byte) bool { } for len(input) > 0 { r, size := charset.DecodeRune(input) - if r == RuneError && size < 2 { + if r == RuneError { return false } input = input[size:]
go/mysql/collations/charset/unicode/utf16.go+3 −3 modified@@ -67,7 +67,7 @@ func (Charset_utf16be) EncodeRune(dst []byte, r rune) int { func (Charset_utf16be) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[1]) | uint16(b[0])<<8 @@ -129,7 +129,7 @@ func (Charset_utf16le) EncodeRune(dst []byte, r rune) int { func (Charset_utf16le) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[0]) | uint16(b[1])<<8 @@ -185,7 +185,7 @@ func (Charset_ucs2) EncodeRune(dst []byte, r rune) int { func (Charset_ucs2) DecodeRune(p []byte) (rune, int) { if len(p) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return rune(p[0])<<8 | rune(p[1]), 2 }
go/mysql/collations/charset/unicode/utf32.go+1 −1 modified@@ -49,7 +49,7 @@ func (Charset_utf32) EncodeRune(dst []byte, r rune) int { func (Charset_utf32) DecodeRune(p []byte) (rune, int) { if len(p) < 4 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return (rune(p[0]) << 24) | (rune(p[1]) << 16) | (rune(p[2]) << 8) | rune(p[3]), 4 }
go/vt/vtgate/evalengine/compiler_asm.go+2 −1 modified@@ -4730,7 +4730,8 @@ func (asm *assembler) Fn_REGEXP_REPLACE_slow(merged collations.TypedCollation, f func (asm *assembler) Introduce(offset int, t sqltypes.Type, col collations.TypedCollation) { asm.emit(func(env *ExpressionEnv) int { - arg := evalToBinary(env.vm.stack[env.vm.sp-offset]) + var arg *evalBytes + arg, env.vm.err = introducerCast(env.vm.stack[env.vm.sp-offset], col.Collation) arg.tt = int16(t) arg.col = col env.vm.stack[env.vm.sp-offset] = arg
go/vt/vtgate/evalengine/compiler_test.go+24 −0 modified@@ -655,6 +655,30 @@ func TestCompilerSingle(t *testing.T) { expression: `1 * unix_timestamp(time('1.0000'))`, result: `DECIMAL(1698098401.0000)`, }, + { + expression: `convert(0xFF using utf16)`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `_utf16 0xFF`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `convert(0xFF using utf32)`, + result: `NULL`, + }, + { + expression: `cast(_utf32 0xFF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x00FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x0000FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, } tz, _ := time.LoadLocation("Europe/Madrid")
go/vt/vtgate/evalengine/expr_collate.go+38 −8 modified@@ -18,6 +18,8 @@ package evalengine import ( "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/mysql/collations/charset" + "vitess.io/vitess/go/mysql/collations/colldata" "vitess.io/vitess/go/sqltypes" vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/vterrors" @@ -131,20 +133,48 @@ func (expr *CollateExpr) compile(c *compiler) (ctype, error) { var _ IR = (*IntroducerExpr)(nil) +func introducerCast(e eval, col collations.ID) (*evalBytes, error) { + if col == collations.CollationBinaryID { + return evalToBinary(e), nil + } + + var bytes []byte + if b, ok := e.(*evalBytes); !ok { + bytes = b.ToRawBytes() + } else { + cs := colldata.Lookup(col).Charset() + bytes = b.bytes + // We only need to pad here for encodings that have a minimum + // character byte width larger than 1, which is all UTF-16 + // variations and UTF-32. + switch cs.(type) { + case charset.Charset_utf16, charset.Charset_utf16le, charset.Charset_ucs2: + if len(bytes)%2 != 0 { + bytes = append([]byte{0}, bytes...) + } + case charset.Charset_utf32: + if mod := len(bytes) % 4; mod != 0 { + bytes = append(make([]byte, 4-mod), bytes...) + } + } + } + typedcol := collations.TypedCollation{ + Collation: col, + Coercibility: collations.CoerceCoercible, + Repertoire: collations.RepertoireASCII, + } + return newEvalText(bytes, typedcol), nil +} + func (expr *IntroducerExpr) eval(env *ExpressionEnv) (eval, error) { e, err := expr.Inner.eval(env) if err != nil { return nil, err } - var b *evalBytes - if expr.TypedCollation.Collation == collations.CollationBinaryID { - b = evalToBinary(e) - } else { - b, err = evalToVarchar(e, expr.TypedCollation.Collation, false) - if err != nil { - return nil, err - } + b, err := introducerCast(e, expr.TypedCollation.Collation) + if err != nil { + return nil, err } b.flag |= flagExplicitCollation return b, nil
go/vt/vtgate/evalengine/translate.go+1 −1 modified@@ -373,7 +373,7 @@ func (ast *astCompiler) translateIntroducerExpr(introduced *sqlparser.Introducer case collations.CollationBinaryID: lit.inner = evalToBinary(lit.inner) default: - lit.inner, err = evalToVarchar(lit.inner, collation, false) + lit.inner, err = introducerCast(lit.inner, collation) if err != nil { return nil, err }
2fd5ba1dbf6eMerge pull request from GHSA-649x-hxfx-57j2
8 files changed · +68 −12
go/mysql/collations/charset/convert.go+1 −1 modified@@ -72,7 +72,7 @@ func convertSlow(dst []byte, dstCharset Charset, src []byte, srcCharset Charset) for len(src) > 0 { cp, width := srcCharset.DecodeRune(src) - if cp == utf8.RuneError && width < 3 { + if cp == utf8.RuneError { failed++ cp = '?' }
go/mysql/collations/charset/helpers.go+1 −1 modified@@ -41,7 +41,7 @@ func Validate(charset Charset, input []byte) bool { } for len(input) > 0 { r, size := charset.DecodeRune(input) - if r == RuneError && size < 2 { + if r == RuneError { return false } input = input[size:]
go/mysql/collations/charset/unicode/utf16.go+3 −3 modified@@ -67,7 +67,7 @@ func (Charset_utf16be) EncodeRune(dst []byte, r rune) int { func (Charset_utf16be) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[1]) | uint16(b[0])<<8 @@ -129,7 +129,7 @@ func (Charset_utf16le) EncodeRune(dst []byte, r rune) int { func (Charset_utf16le) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[0]) | uint16(b[1])<<8 @@ -185,7 +185,7 @@ func (Charset_ucs2) EncodeRune(dst []byte, r rune) int { func (Charset_ucs2) DecodeRune(p []byte) (rune, int) { if len(p) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return rune(p[0])<<8 | rune(p[1]), 2 }
go/mysql/collations/charset/unicode/utf32.go+1 −1 modified@@ -49,7 +49,7 @@ func (Charset_utf32) EncodeRune(dst []byte, r rune) int { func (Charset_utf32) DecodeRune(p []byte) (rune, int) { if len(p) < 4 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return (rune(p[0]) << 24) | (rune(p[1]) << 16) | (rune(p[2]) << 8) | rune(p[3]), 4 }
go/vt/vtgate/evalengine/compiler_asm.go+2 −1 modified@@ -4733,7 +4733,8 @@ func (asm *assembler) Fn_REGEXP_REPLACE_slow(merged collations.TypedCollation, f func (asm *assembler) Introduce(offset int, t sqltypes.Type, col collations.TypedCollation) { asm.emit(func(env *ExpressionEnv) int { - arg := evalToBinary(env.vm.stack[env.vm.sp-offset]) + var arg *evalBytes + arg, env.vm.err = introducerCast(env.vm.stack[env.vm.sp-offset], col.Collation) arg.tt = int16(t) arg.col = col env.vm.stack[env.vm.sp-offset] = arg
go/vt/vtgate/evalengine/compiler_test.go+24 −0 modified@@ -503,6 +503,30 @@ func TestCompilerSingle(t *testing.T) { expression: `week('2024-12-31', 5)`, result: `INT64(53)`, }, + { + expression: `convert(0xFF using utf16)`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `_utf16 0xFF`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `convert(0xFF using utf32)`, + result: `NULL`, + }, + { + expression: `cast(_utf32 0xFF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x00FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x0000FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, } for _, tc := range testCases {
go/vt/vtgate/evalengine/expr_collate.go+35 −4 modified@@ -18,6 +18,7 @@ package evalengine import ( "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/mysql/collations/charset" "vitess.io/vitess/go/mysql/collations/colldata" "vitess.io/vitess/go/sqltypes" querypb "vitess.io/vitess/go/vt/proto/query" @@ -217,15 +218,45 @@ func (ca *collationAggregation) result() collations.TypedCollation { var _ Expr = (*IntroducerExpr)(nil) +func introducerCast(e eval, col collations.ID) (*evalBytes, error) { + if col == collations.CollationBinaryID { + return evalToBinary(e), nil + } + + var bytes []byte + if b, ok := e.(*evalBytes); !ok { + bytes = b.ToRawBytes() + } else { + cs := colldata.Lookup(col).Charset() + bytes = b.bytes + // We only need to pad here for encodings that have a minimum + // character byte width larger than 1, which is all UTF-16 + // variations and UTF-32. + switch cs.(type) { + case charset.Charset_utf16, charset.Charset_utf16le, charset.Charset_ucs2: + if len(bytes)%2 != 0 { + bytes = append([]byte{0}, bytes...) + } + case charset.Charset_utf32: + if mod := len(bytes) % 4; mod != 0 { + bytes = append(make([]byte, 4-mod), bytes...) + } + } + } + typedcol := collations.TypedCollation{ + Collation: col, + Coercibility: collations.CoerceCoercible, + Repertoire: collations.RepertoireASCII, + } + return newEvalText(bytes, typedcol), nil +} + func (expr *IntroducerExpr) eval(env *ExpressionEnv) (eval, error) { e, err := expr.Inner.eval(env) if err != nil { return nil, err } - if expr.TypedCollation.Collation == collations.CollationBinaryID { - return evalToBinary(e), nil - } - return evalToVarchar(e, expr.TypedCollation.Collation, false) + return introducerCast(e, expr.TypedCollation.Collation) } func (expr *IntroducerExpr) typeof(env *ExpressionEnv, fields []*querypb.Field) (sqltypes.Type, typeFlag) {
go/vt/vtgate/evalengine/translate.go+1 −1 modified@@ -361,7 +361,7 @@ func (ast *astCompiler) translateIntroducerExpr(introduced *sqlparser.Introducer case collations.CollationBinaryID: lit.inner = evalToBinary(lit.inner) default: - lit.inner, err = evalToVarchar(lit.inner, collation, false) + lit.inner, err = introducerCast(lit.inner, collation) if err != nil { return nil, err }
9df4b66550e4Merge pull request from GHSA-649x-hxfx-57j2
8 files changed · +71 −16
go/mysql/collations/charset/convert.go+1 −1 modified@@ -72,7 +72,7 @@ func convertSlow(dst []byte, dstCharset Charset, src []byte, srcCharset Charset) for len(src) > 0 { cp, width := srcCharset.DecodeRune(src) - if cp == utf8.RuneError && width < 3 { + if cp == utf8.RuneError { failed++ cp = '?' }
go/mysql/collations/charset/helpers.go+1 −1 modified@@ -41,7 +41,7 @@ func Validate(charset Charset, input []byte) bool { } for len(input) > 0 { r, size := charset.DecodeRune(input) - if r == RuneError && size < 2 { + if r == RuneError { return false } input = input[size:]
go/mysql/collations/charset/unicode/utf16.go+3 −3 modified@@ -67,7 +67,7 @@ func (Charset_utf16be) EncodeRune(dst []byte, r rune) int { func (Charset_utf16be) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[1]) | uint16(b[0])<<8 @@ -129,7 +129,7 @@ func (Charset_utf16le) EncodeRune(dst []byte, r rune) int { func (Charset_utf16le) DecodeRune(b []byte) (rune, int) { if len(b) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(b) } r1 := uint16(b[0]) | uint16(b[1])<<8 @@ -185,7 +185,7 @@ func (Charset_ucs2) EncodeRune(dst []byte, r rune) int { func (Charset_ucs2) DecodeRune(p []byte) (rune, int) { if len(p) < 2 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return rune(p[0])<<8 | rune(p[1]), 2 }
go/mysql/collations/charset/unicode/utf32.go+1 −1 modified@@ -49,7 +49,7 @@ func (Charset_utf32) EncodeRune(dst []byte, r rune) int { func (Charset_utf32) DecodeRune(p []byte) (rune, int) { if len(p) < 4 { - return utf8.RuneError, 0 + return utf8.RuneError, len(p) } return (rune(p[0]) << 24) | (rune(p[1]) << 16) | (rune(p[2]) << 8) | rune(p[3]), 4 }
go/vt/vtgate/evalengine/compiler_asm.go+2 −1 modified@@ -5068,7 +5068,8 @@ func (asm *assembler) Fn_REGEXP_REPLACE_slow(merged collations.TypedCollation, f func (asm *assembler) Introduce(offset int, t sqltypes.Type, col collations.TypedCollation) { asm.emit(func(env *ExpressionEnv) int { - arg := evalToBinary(env.vm.stack[env.vm.sp-offset]) + var arg *evalBytes + arg, env.vm.err = introducerCast(env.vm.stack[env.vm.sp-offset], col.Collation) arg.tt = int16(t) arg.col = col env.vm.stack[env.vm.sp-offset] = arg
go/vt/vtgate/evalengine/compiler_test.go+24 −0 modified@@ -699,6 +699,30 @@ func TestCompilerSingle(t *testing.T) { result: `DATETIME("2023-10-24 12:00:00.000000")`, typeWanted: evalengine.NewTypeEx(sqltypes.Datetime, collations.CollationBinaryID, false, 6, 0, nil), }, + { + expression: `convert(0xFF using utf16)`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `_utf16 0xFF`, + result: `VARCHAR("ÿ")`, + }, + { + expression: `convert(0xFF using utf32)`, + result: `NULL`, + }, + { + expression: `cast(_utf32 0xFF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x00FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, + { + expression: `cast(_utf32 0x0000FF as binary)`, + result: `VARBINARY("\x00\x00\x00\xff")`, + }, } tz, _ := time.LoadLocation("Europe/Madrid")
go/vt/vtgate/evalengine/expr_collate.go+38 −8 modified@@ -18,6 +18,8 @@ package evalengine import ( "vitess.io/vitess/go/mysql/collations" + "vitess.io/vitess/go/mysql/collations/charset" + "vitess.io/vitess/go/mysql/collations/colldata" "vitess.io/vitess/go/sqltypes" vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" "vitess.io/vitess/go/vt/vterrors" @@ -131,20 +133,48 @@ func (expr *CollateExpr) compile(c *compiler) (ctype, error) { var _ IR = (*IntroducerExpr)(nil) +func introducerCast(e eval, col collations.ID) (*evalBytes, error) { + if col == collations.CollationBinaryID { + return evalToBinary(e), nil + } + + var bytes []byte + if b, ok := e.(*evalBytes); !ok { + bytes = b.ToRawBytes() + } else { + cs := colldata.Lookup(col).Charset() + bytes = b.bytes + // We only need to pad here for encodings that have a minimum + // character byte width larger than 1, which is all UTF-16 + // variations and UTF-32. + switch cs.(type) { + case charset.Charset_utf16, charset.Charset_utf16le, charset.Charset_ucs2: + if len(bytes)%2 != 0 { + bytes = append([]byte{0}, bytes...) + } + case charset.Charset_utf32: + if mod := len(bytes) % 4; mod != 0 { + bytes = append(make([]byte, 4-mod), bytes...) + } + } + } + typedcol := collations.TypedCollation{ + Collation: col, + Coercibility: collations.CoerceCoercible, + Repertoire: collations.RepertoireASCII, + } + return newEvalText(bytes, typedcol), nil +} + func (expr *IntroducerExpr) eval(env *ExpressionEnv) (eval, error) { e, err := expr.Inner.eval(env) if err != nil { return nil, err } - var b *evalBytes - if expr.TypedCollation.Collation == collations.CollationBinaryID { - b = evalToBinary(e) - } else { - b, err = evalToVarchar(e, expr.TypedCollation.Collation, false) - if err != nil { - return nil, err - } + b, err := introducerCast(e, expr.TypedCollation.Collation) + if err != nil { + return nil, err } b.flag |= flagExplicitCollation return b, nil
go/vt/vtgate/evalengine/translate.go+1 −1 modified@@ -373,7 +373,7 @@ func (ast *astCompiler) translateIntroducerExpr(introduced *sqlparser.Introducer case collations.CollationBinaryID: lit.inner = evalToBinary(lit.inner) default: - lit.inner, err = evalToVarchar(lit.inner, collation, false) + lit.inner, err = introducerCast(lit.inner, collation) if err != nil { return nil, err }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-649x-hxfx-57j2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-32886ghsaADVISORY
- github.com/vitessio/vitess/blob/8f6cfaaa643a08dc111395a75a2d250ee746cfa8/go/mysql/collations/charset/convert.gonvdWEB
- github.com/vitessio/vitess/blob/8f6cfaaa643a08dc111395a75a2d250ee746cfa8/go/mysql/collations/charset/unicode/utf16.gonvdWEB
- github.com/vitessio/vitess/commit/2fd5ba1dbf6e9b32fdfdaf869d130066b1b5c0dfnvdWEB
- github.com/vitessio/vitess/commit/9df4b66550e46b5d7079e21ed0e1b0f49f92b055nvdWEB
- github.com/vitessio/vitess/commit/c46dc5b6a4329a10589ca928392218d96031ac8dnvdWEB
- github.com/vitessio/vitess/commit/d438adf7e34a6cf00fe441db80842ec669a99202nvdWEB
- github.com/vitessio/vitess/security/advisories/GHSA-649x-hxfx-57j2nvdWEB
News mentions
0No linked articles in our index yet.