From 0ee7a271d38da6a65e81d7dafbef7fee3d2b0a66 Mon Sep 17 00:00:00 2001 From: Kevin Wan Date: Tue, 26 Sep 2023 21:46:34 +0800 Subject: [PATCH] fix: avoid float overflow in mapping.Unmarshal (#3590) --- core/mapping/unmarshaler.go | 14 ++- core/mapping/unmarshaler_test.go | 152 ++++++++++++++++++++++++++++++- core/mapping/utils.go | 39 ++++++-- 3 files changed, 192 insertions(+), 13 deletions(-) diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index 54ca804a..57a3d7e8 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "reflect" "strconv" "strings" @@ -614,7 +615,18 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type if err := setValueFromString(typeKind, target, v.String()); err != nil { return err } - case reflect.Float32, reflect.Float64: + case reflect.Float32: + fValue, err := v.Float64() + if err != nil { + return err + } + + if fValue > math.MaxFloat32 { + return float32OverflowError(v.String()) + } + + target.SetFloat(fValue) + case reflect.Float64: fValue, err := v.Float64() if err != nil { return err diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index b39c6ab8..2ac19aa4 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -731,6 +731,34 @@ func TestUnmarshalInt32WithOverflow(t *testing.T) { }) } +func TestUnmarshalInt64WithOverflow(t *testing.T) { + t.Run("int64 from string", func(t *testing.T) { + type inner struct { + Value int64 `key:"int,string"` + } + + m := map[string]any{ + "int": "18446744073709551616", // overflow, 1 << 64 + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("int64 from json.Number", func(t *testing.T) { + type inner struct { + Value int64 `key:"int,string"` + } + + m := map[string]any{ + "int": json.Number("18446744073709551616"), // overflow, 1 << 64 + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) +} + func TestUnmarshalUint8WithOverflow(t *testing.T) { t.Run("uint8 from string", func(t *testing.T) { type inner struct { @@ -758,7 +786,7 @@ func TestUnmarshalUint8WithOverflow(t *testing.T) { assert.Error(t, UnmarshalKey(m, &in)) }) - t.Run("uint8 from json.Number", func(t *testing.T) { + t.Run("uint8 from json.Number with negative", func(t *testing.T) { type inner struct { Value uint8 `key:"int"` } @@ -812,7 +840,7 @@ func TestUnmarshalUint16WithOverflow(t *testing.T) { assert.Error(t, UnmarshalKey(m, &in)) }) - t.Run("uint16 from json.Number", func(t *testing.T) { + t.Run("uint16 from json.Number with negative", func(t *testing.T) { type inner struct { Value uint16 `key:"int"` } @@ -866,7 +894,7 @@ func TestUnmarshalUint32WithOverflow(t *testing.T) { assert.Error(t, UnmarshalKey(m, &in)) }) - t.Run("uint32 from json.Number", func(t *testing.T) { + t.Run("uint32 from json.Number with negative", func(t *testing.T) { type inner struct { Value uint32 `key:"int"` } @@ -893,6 +921,116 @@ func TestUnmarshalUint32WithOverflow(t *testing.T) { }) } +func TestUnmarshalUint64WithOverflow(t *testing.T) { + t.Run("uint64 from string", func(t *testing.T) { + type inner struct { + Value uint64 `key:"int,string"` + } + + m := map[string]any{ + "int": "18446744073709551616", // overflow, 1 << 64 + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("uint64 from json.Number", func(t *testing.T) { + type inner struct { + Value uint64 `key:"int,string"` + } + + m := map[string]any{ + "int": json.Number("18446744073709551616"), // overflow, 1 << 64 + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) +} + +func TestUnmarshalFloat32WithOverflow(t *testing.T) { + t.Run("float32 from string greater than float64", func(t *testing.T) { + type inner struct { + Value float32 `key:"float,string"` + } + + m := map[string]any{ + "float": "1.79769313486231570814527423731704356798070e+309", // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("float32 from string greater than float32", func(t *testing.T) { + type inner struct { + Value float32 `key:"float,string"` + } + + m := map[string]any{ + "float": "1.79769313486231570814527423731704356798070e+300", // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("float32 from json.Number greater than float64", func(t *testing.T) { + type inner struct { + Value float32 `key:"float"` + } + + m := map[string]any{ + "float": json.Number("1.79769313486231570814527423731704356798070e+309"), // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("float32 from json.Number greater than float32", func(t *testing.T) { + type inner struct { + Value float32 `key:"float"` + } + + m := map[string]any{ + "float": json.Number("1.79769313486231570814527423731704356798070e+300"), // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) +} + +func TestUnmarshalFloat64WithOverflow(t *testing.T) { + t.Run("float64 from string greater than float64", func(t *testing.T) { + type inner struct { + Value float64 `key:"float,string"` + } + + m := map[string]any{ + "float": "1.79769313486231570814527423731704356798070e+309", // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) + + t.Run("float32 from json.Number greater than float64", func(t *testing.T) { + type inner struct { + Value float64 `key:"float"` + } + + m := map[string]any{ + "float": json.Number("1.79769313486231570814527423731704356798070e+309"), // overflow + } + + var in inner + assert.Error(t, UnmarshalKey(m, &in)) + }) +} + func TestUnmarshalBoolSliceRequired(t *testing.T) { type inner struct { Bools []bool `key:"bools"` @@ -1119,16 +1257,20 @@ func TestUnmarshalFloat(t *testing.T) { type inner struct { Float32 float32 `key:"float32"` Float32Str float32 `key:"float32str,string"` + Float32Num float32 `key:"float32num"` Float64 float64 `key:"float64"` Float64Str float64 `key:"float64str,string"` + Float64Num float64 `key:"float64num"` DefaultFloat float32 `key:"defaultfloat,default=5.5"` Optional float32 `key:",optional"` } m := map[string]any{ "float32": float32(1.5), "float32str": "2.5", - "float64": float64(3.5), + "float32num": json.Number("2.6"), + "float64": 3.5, "float64str": "4.5", + "float64num": json.Number("4.6"), } var in inner @@ -1136,8 +1278,10 @@ func TestUnmarshalFloat(t *testing.T) { if ast.NoError(UnmarshalKey(m, &in)) { ast.Equal(float32(1.5), in.Float32) ast.Equal(float32(2.5), in.Float32Str) + ast.Equal(float32(2.6), in.Float32Num) ast.Equal(3.5, in.Float64) ast.Equal(4.5, in.Float64Str) + ast.Equal(4.6, in.Float64Num) ast.Equal(float32(5.5), in.DefaultFloat) } } diff --git a/core/mapping/utils.go b/core/mapping/utils.go index b041d8d0..e00a61ea 100644 --- a/core/mapping/utils.go +++ b/core/mapping/utils.go @@ -34,7 +34,6 @@ const ( var ( errUnsupportedType = errors.New("unsupported type on setting field value") - errNumberOverflow = errors.New("integer overflow") errNumberRange = errors.New("wrong number range setting") optionsCache = make(map[string]optionsCacheValue) cacheLock sync.RWMutex @@ -43,6 +42,10 @@ var ( ) type ( + integer interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 + } + optionsCacheValue struct { key string options *fieldOptions @@ -104,21 +107,32 @@ func convertTypeFromString(kind reflect.Kind, str string) (any, error) { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: intValue, err := strconv.ParseInt(str, 10, 64) if err != nil { - return 0, fmt.Errorf("the value %q cannot be parsed as int", str) + return 0, err } return intValue, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: uintValue, err := strconv.ParseUint(str, 10, 64) if err != nil { - return 0, fmt.Errorf("the value %q cannot be parsed as uint", str) + return 0, err } return uintValue, nil - case reflect.Float32, reflect.Float64: + case reflect.Float32: floatValue, err := strconv.ParseFloat(str, 64) if err != nil { - return 0, fmt.Errorf("the value %q cannot be parsed as float", str) + return 0, err + } + + if floatValue > math.MaxFloat32 { + return 0, float32OverflowError(str) + } + + return floatValue, nil + case reflect.Float64: + floatValue, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0, err } return floatValue, nil @@ -216,6 +230,10 @@ func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) { return false, nil } +func intOverflowError[T integer](v T, kind reflect.Kind) error { + return fmt.Errorf("parsing \"%d\" as %s: value out of range", v, kind.String()) +} + func isLeftInclude(b byte) (bool, error) { switch b { case '[': @@ -238,6 +256,10 @@ func isRightInclude(b byte) (bool, error) { } } +func float32OverflowError(str string) error { + return fmt.Errorf("parsing %q as float32: value out of range", str) +} + func maybeNewValue(fieldType reflect.Type, value reflect.Value) { if fieldType.Kind() == reflect.Ptr && value.IsNil() { value.Set(reflect.New(value.Type().Elem())) @@ -486,7 +508,7 @@ func parseSegments(val string) []string { func setIntValue(value reflect.Value, v any, min, max int64) error { iv := v.(int64) if iv < min || iv > max { - return errNumberOverflow + return intOverflowError(iv, value.Kind()) } value.SetInt(iv) @@ -534,7 +556,7 @@ func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) err func setUintValue(value reflect.Value, v any, boundary uint64) error { iv := v.(uint64) if iv > boundary { - return errNumberOverflow + return intOverflowError(iv, value.Kind()) } value.SetUint(iv) @@ -615,7 +637,8 @@ func usingDifferentKeys(key string, field reflect.StructField) bool { return false } -func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error { +func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, + opts *fieldOptionsWithContext) error { if !value.CanSet() { return errValueNotSettable }