From 5380a6467d739a2d0c08030abd1181836301e870 Mon Sep 17 00:00:00 2001 From: Sam Mulube Date: Tue, 27 Oct 2026 22:01:39 -0100 Subject: [PATCH] Prevent oversized BodyLength crashing process This vulnerability allows a peer to crash a quickfix go server by sending a message with BodyLength too large to fit in an int. There is no recover on the goroutine that sees this panic so it would end the whole process along with all other hosted sessions --- fix_int.go | 38 ++++++++++++++++++++++++++++++++++++++ fix_int_test.go | 18 ++++++++++++++++++ parser.go | 3 ++- parser_test.go | 13 +++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/fix_int.go b/fix_int.go index 6bd968003..63f52512c 100644 --- a/fix_int.go +++ b/fix_int.go @@ -17,6 +17,7 @@ package quickfix import ( "errors" + "math" "strconv" ) @@ -27,10 +28,20 @@ const ( // ASCII numbers 0-9. ascii0 = 48 ascii9 = 57 + + // maxSafeDigits is the most digits that cannot overflow an int: 18 when + // int is 64 bits, 9 when it is 32. + maxSafeDigits = strconv.IntSize/64*9 + 9 ) // atoi is similar to the function in strconv, but is tuned for ints appearing in FIX field types. func atoi(d []byte) (int, error) { + // Checking the length here rather than in parseUInt keeps parseUInt small + // enough to inline. + if len(d) > maxSafeDigits { + return parseLongInt(d) + } + if d[0] == asciiMinus { n, err := parseUInt(d[1:]) return (-1) * n, err @@ -58,6 +69,33 @@ func parseUInt(d []byte) (n int, err error) { return } +// parseLongInt is atoi for values long enough to overflow an int, which it +// reports as an error. +func parseLongInt(d []byte) (n int, err error) { + neg := d[0] == asciiMinus + if neg { + d = d[1:] + } + + for _, dec := range d { + if dec < ascii0 || dec > ascii9 { + err = errors.New("invalid format") + return + } + + digit := int(dec) - ascii0 + if n > (math.MaxInt-digit)/10 { + return 0, errors.New("value out of range") + } + n = n*10 + digit + } + + if neg { + n = -n + } + return +} + // FIXInt is a FIX Int Value, implements FieldValue. type FIXInt int diff --git a/fix_int_test.go b/fix_int_test.go index caa9b7840..81062b2c4 100644 --- a/fix_int_test.go +++ b/fix_int_test.go @@ -16,6 +16,8 @@ package quickfix import ( + "math" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -37,6 +39,22 @@ func TestFIXInt_Read(t *testing.T) { assert.NotNil(t, err, "Unexpected error") } +func TestFIXInt_ReadRange(t *testing.T) { + var field FIXInt + assert.Nil(t, field.Read([]byte(strconv.Itoa(math.MaxInt)))) + assert.Equal(t, math.MaxInt, int(field)) + + assert.Nil(t, field.Read([]byte(strconv.Itoa(-math.MaxInt)))) + assert.Equal(t, -math.MaxInt, int(field)) + + assert.Nil(t, field.Read([]byte("0000000000000000000000015"))) + assert.Equal(t, 15, int(field)) + + for _, v := range []string{"9223372036854775808", "-9223372036854775809", "99999999999999999999"} { + assert.NotNil(t, field.Read([]byte(v)), v) + } +} + func TestFIXInt_Int(t *testing.T) { f := FIXInt(4) assert.Equal(t, 4, f.Int()) diff --git a/parser.go b/parser.go index 38ac19ddc..4a00ef771 100644 --- a/parser.go +++ b/parser.go @@ -19,6 +19,7 @@ import ( "bytes" "errors" "io" + "math" "time" ) @@ -132,7 +133,7 @@ func (p *parser) jumpLength() (int, error) { return length, err } - if length <= 0 { + if length <= 0 || length > math.MaxInt-offset { return length, errors.New("Invalid length") } diff --git a/parser_test.go b/parser_test.go index 34bca808d..0957f2ed9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -59,6 +59,19 @@ func (s *ParserSuite) TestOverflowLength() { s.NotNil(err) } +func (s *ParserSuite) TestLengthPastMaxInt() { + for _, length := range []string{ + "9223372036854775807", + "9223372036854775808", + "119999999999999999999999999999999999999999999999999999999999970", + } { + s.parser = new(parser) + s.reader = strings.NewReader("8=FIX.4.29=" + length + "") + _, err := s.ReadMessage() + s.NotNil(err, length) + } +} + func (s *ParserSuite) TestJumpLength() { stream := "8=FIXT.1.19=11135=D34=449=TW52=20140511-23:10:3456=ISLD11=ID21=340=154=155=INTC60=20140511-23:10:3410=2348=FIXT.1.19=9535=D34=549=TW52=20140511-23:10:3456=ISLD11=ID21=340=154=155=INTC60=20140511-23:10:3410=198"