Repository: zeromicro/go-zero
Pull request #3590 (2023-09-26 13:17:53)

fix: avoid float overflow in mapping.Unmarshal


Patch:
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/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, 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, fmt.Errorf("the value %q cannot be parsed as float", str)
+			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
 	}


Test patch:
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)
 	}
 }