From eaedce9a8b64ce207e25553232bcc7541e0044e7 Mon Sep 17 00:00:00 2001 From: czechbol Date: Wed, 4 Sep 2024 16:09:54 +0200 Subject: [PATCH] Improvement the int conversion overflow logic to handle bound checks (#1194) * add test cases Signed-off-by: czechbol * fix bounds check logic Signed-off-by: czechbol * tweak test cases Signed-off-by: czechbol * fix codestyle Signed-off-by: czechbol * improve bounds check logic Signed-off-by: czechbol * max recursion depth Signed-off-by: czechbol * add test case for len function Signed-off-by: czechbol * relax len function bounds checks Co-authored-by: Ben Krieger * handle cases when convert instruction is after the if blocks Signed-off-by: czechbol * improve range check discovery, add tests Signed-off-by: czechbol * refactor for readability Signed-off-by: czechbol * add cap function test Signed-off-by: czechbol * calculate signed min without throwing overflow warnings Signed-off-by: czechbol * perform bounds checks int size calculations Signed-off-by: czechbol * basic equal operator logic Signed-off-by: czechbol * uintptr -> unsafe.Pointer test case Signed-off-by: czechbol * fix review comments Signed-off-by: czechbol * Rebase and fix go module Change-Id: I8da6495eaaf25b1739389aa98492bd7df338085b Signed-off-by: Cosmin Cojocar * fix false positive for negated value Signed-off-by: czechbol * fix range conditions Signed-off-by: czechbol * Ignore the golangci/gosec G115 warning Change-Id: I0db56cb0a5f9ab6e815e2480ec0b66d7061b23d3 Signed-off-by: Cosmin Cojocar --------- Signed-off-by: czechbol Signed-off-by: Cosmin Cojocar Co-authored-by: Ben Krieger Co-authored-by: Cosmin Cojocar --- analyzers/conversion_overflow.go | 437 ++++++++++++++++++++++---- analyzers/conversion_overflow_test.go | 139 ++++++++ go.mod | 1 + go.sum | 2 + testutils/g115_samples.go | 330 ++++++++++++++++++- 5 files changed, 838 insertions(+), 71 deletions(-) create mode 100644 analyzers/conversion_overflow_test.go diff --git a/analyzers/conversion_overflow.go b/analyzers/conversion_overflow.go index 1449f94..3ef4825 100644 --- a/analyzers/conversion_overflow.go +++ b/analyzers/conversion_overflow.go @@ -17,9 +17,12 @@ package analyzers import ( "fmt" "go/token" + "math" "regexp" "strconv" + "strings" + "golang.org/x/exp/constraints" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/ssa" @@ -27,6 +30,30 @@ import ( "github.com/securego/gosec/v2/issue" ) +type integer struct { + signed bool + size int + min int + max uint +} + +type rangeResult struct { + minValue int + maxValue uint + explixitPositiveVals []uint + explicitNegativeVals []int + isRangeCheck bool + convertFound bool +} + +type branchResults struct { + minValue *int + maxValue *uint + explixitPositiveVals []uint + explicitNegativeVals []int + convertFound bool +} + func newConversionOverflowAnalyzer(id string, description string) *analysis.Analyzer { return &analysis.Analyzer{ Name: id, @@ -50,10 +77,10 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) { case *ssa.Convert: src := instr.X.Type().Underlying().String() dst := instr.Type().Underlying().String() - if isSafeConversion(instr) { - continue - } if isIntOverflow(src, dst) { + if isSafeConversion(instr) { + continue + } issue := newIssue(pass.Analyzer.Name, fmt.Sprintf("integer overflow conversion %s -> %s", src, dst), pass.Fset, @@ -74,23 +101,90 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) { return nil, nil } +func isIntOverflow(src string, dst string) bool { + srcInt, err := parseIntType(src) + if err != nil { + return false + } + + dstInt, err := parseIntType(dst) + if err != nil { + return false + } + + return srcInt.min < dstInt.min || srcInt.max > dstInt.max +} + +func parseIntType(intType string) (integer, error) { + re := regexp.MustCompile(`^(?Pu?int)(?P\d{1,2})?$`) + matches := re.FindStringSubmatch(intType) + if matches == nil { + return integer{}, fmt.Errorf("no integer type match found for %s", intType) + } + + it := matches[re.SubexpIndex("type")] + is := matches[re.SubexpIndex("size")] + + signed := it == "int" + + // use default system int type in case size is not present in the type. + intSize := strconv.IntSize + if is != "" { + var err error + intSize, err = strconv.Atoi(is) + if err != nil { + return integer{}, fmt.Errorf("failed to parse the integer type size: %w", err) + } + } + + if intSize != 8 && intSize != 16 && intSize != 32 && intSize != 64 && is != "" { + return integer{}, fmt.Errorf("invalid bit size: %d", intSize) + } + + var min int + var max uint + + if signed { + shiftAmount := intSize - 1 + + // Perform a bounds check. + if shiftAmount < 0 { + return integer{}, fmt.Errorf("invalid shift amount: %d", shiftAmount) + } + + max = (1 << uint(shiftAmount)) - 1 + min = -1 << (intSize - 1) + + } else { + max = (1 << uint(intSize)) - 1 + min = 0 + } + + return integer{ + signed: signed, + size: intSize, + min: min, + max: max, + }, nil +} + func isSafeConversion(instr *ssa.Convert) bool { dstType := instr.Type().Underlying().String() - // Check for constant conversions + // Check for constant conversions. if constVal, ok := instr.X.(*ssa.Const); ok { if isConstantInRange(constVal, dstType) { return true } } - // Check for explicit range checks - if hasExplicitRangeCheck(instr) { + // Check for string to integer conversions with specified bit size. + if isStringToIntConversion(instr, dstType) { return true } - // Check for string to integer conversions with specified bit size - if isStringToIntConversion(instr, dstType) { + // Check for explicit range checks. + if hasExplicitRangeCheck(instr, dstType) { return true } @@ -114,22 +208,8 @@ func isConstantInRange(constVal *ssa.Const, dstType string) bool { return value >= 0 && value <= (1< dstInt.min && maxValue < dstInt.max { + return true + } + + visitedIfs := make(map[*ssa.If]bool) + for _, block := range instr.Parent().Blocks { + for _, blockInstr := range block.Instrs { + switch v := blockInstr.(type) { + case *ssa.If: + result := getResultRange(v, instr, visitedIfs) + if result.isRangeCheck { + minValue = max(minValue, &result.minValue) + maxValue = min(maxValue, &result.maxValue) + explicitPositiveVals = append(explicitPositiveVals, result.explixitPositiveVals...) + explicitNegativeVals = append(explicitNegativeVals, result.explicitNegativeVals...) + } + case *ssa.Call: + // These function return an int of a guaranteed size. + if v != instr.X { + continue + } + if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin { + switch fn.Name() { + case "len", "cap": + minValue = 0 + } + } + } + + if explicitValsInRange(explicitPositiveVals, explicitNegativeVals, dstInt) { + return true + } else if minValue >= dstInt.min && maxValue <= dstInt.max { + return true + } + } + } + return false } -func parseIntType(intType string) (integer, error) { - re := regexp.MustCompile(`(?Pu?int)(?P\d{1,2})?`) - matches := re.FindStringSubmatch(intType) - if matches == nil { - return integer{}, fmt.Errorf("no integer type match found for %s", intType) +// getResultRange is a recursive function that walks the branches of the if statement to find the range of the variable. +func getResultRange(ifInstr *ssa.If, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) rangeResult { + if visitedIfs[ifInstr] { + return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint} + } + visitedIfs[ifInstr] = true + + cond := ifInstr.Cond + binOp, ok := cond.(*ssa.BinOp) + if !ok || !isRangeCheck(binOp, instr.X) { + return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint} } - it := matches[re.SubexpIndex("type")] - is := matches[re.SubexpIndex("size")] - - signed := false - if it == "int" { - signed = true + result := rangeResult{ + minValue: math.MinInt, + maxValue: math.MaxUint, + isRangeCheck: true, } - // use default system int type in case size is not present in the type - intSize := strconv.IntSize - if is != "" { - var err error - intSize, err = strconv.Atoi(is) - if err != nil { - return integer{}, fmt.Errorf("failed to parse the integer type size: %w", err) + thenBounds := walkBranchForConvert(ifInstr.Block().Succs[0], instr, visitedIfs) + elseBounds := walkBranchForConvert(ifInstr.Block().Succs[1], instr, visitedIfs) + + updateResultFromBinOp(&result, binOp, instr, thenBounds.convertFound) + + if thenBounds.convertFound { + result.convertFound = true + result.minValue = max(result.minValue, thenBounds.minValue) + result.maxValue = min(result.maxValue, thenBounds.maxValue) + result.explixitPositiveVals = append(result.explixitPositiveVals, thenBounds.explixitPositiveVals...) + result.explicitNegativeVals = append(result.explicitNegativeVals, thenBounds.explicitNegativeVals...) + } else if elseBounds.convertFound { + result.convertFound = true + result.minValue = max(result.minValue, elseBounds.minValue) + result.maxValue = min(result.maxValue, elseBounds.maxValue) + result.explixitPositiveVals = append(result.explixitPositiveVals, elseBounds.explixitPositiveVals...) + result.explicitNegativeVals = append(result.explicitNegativeVals, elseBounds.explicitNegativeVals...) + } + + return result +} + +// updateResultFromBinOp updates the rangeResult based on the BinOp instruction and the location of the Convert instruction. +func updateResultFromBinOp(result *rangeResult, binOp *ssa.BinOp, instr *ssa.Convert, successPathConvert bool) { + x, y := binOp.X, binOp.Y + operandsFlipped := false + + compareVal, op := getRealValueFromOperation(instr.X) + if x != compareVal { + y, operandsFlipped = x, true + } + + constVal, ok := y.(*ssa.Const) + if !ok { + return + } + + switch binOp.Op { + case token.LEQ, token.LSS: + updateMinMaxForLessOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert) + case token.GEQ, token.GTR: + updateMinMaxForGreaterOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert) + case token.EQL: + if !successPathConvert { + break + } + + // Determine if the constant value is positive or negative. + if strings.Contains(constVal.String(), "-") { + result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64())) + } else { + result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64())) + } + + case token.NEQ: + if successPathConvert { + break + } + + // Determine if the constant value is positive or negative. + if strings.Contains(constVal.String(), "-") { + result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64())) + } else { + result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64())) } } - return integer{signed: signed, size: intSize}, nil + if op == "neg" { + min := result.minValue + max := result.maxValue + + if min >= 0 { + result.maxValue = uint(min) + } + if max <= math.MaxInt { + result.minValue = int(max) //nolint:gosec + } + } } -func isIntOverflow(src string, dst string) bool { - srcInt, err := parseIntType(src) - if err != nil { - return false +func updateMinMaxForLessOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) { + // If the success path has a conversion and the operands are not flipped, then the constant value is the maximum value. + if successPathConvert && !operandsFlipped { + result.maxValue = uint(constVal.Uint64()) + if op == token.LEQ { + result.maxValue-- + } + } else { + result.minValue = int(constVal.Int64()) + if op == token.GTR { + result.minValue++ + } + } +} + +func updateMinMaxForGreaterOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) { + // If the success path has a conversion and the operands are not flipped, then the constant value is the minimum value. + if successPathConvert && !operandsFlipped { + result.minValue = int(constVal.Int64()) + if op == token.GEQ { + result.minValue++ + } + } else { + result.maxValue = uint(constVal.Uint64()) + if op == token.LSS { + result.maxValue-- + } + } +} + +// walkBranchForConvert walks the branch of the if statement to find the range of the variable and where the conversion is. +func walkBranchForConvert(block *ssa.BasicBlock, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) branchResults { + bounds := branchResults{} + + for _, blockInstr := range block.Instrs { + switch v := blockInstr.(type) { + case *ssa.If: + result := getResultRange(v, instr, visitedIfs) + bounds.convertFound = bounds.convertFound || result.convertFound + + if result.isRangeCheck { + bounds.minValue = toPtr(max(result.minValue, bounds.minValue)) + bounds.maxValue = toPtr(min(result.maxValue, bounds.maxValue)) + } + case *ssa.Call: + if v == instr.X { + if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin && (fn.Name() == "len" || fn.Name() == "cap") { + bounds.minValue = toPtr(0) + } + } + case *ssa.Convert: + if v == instr { + bounds.convertFound = true + return bounds + } + } } - dstInt, err := parseIntType(dst) - if err != nil { - return false - } + return bounds +} - // converting uint to int of the same size or smaller might lead to overflow - if !srcInt.signed && dstInt.signed && dstInt.size <= srcInt.size { - return true - } - // converting uint to unit of a smaller size might lead to overflow - if !srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size { - return true - } - // converting int to int of a smaller size might lead to overflow - if srcInt.signed && dstInt.signed && dstInt.size < srcInt.size { - return true - } - // converting int to uint of a smaller size might lead to overflow - if srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size && srcInt.size-dstInt.size > 8 { - return true - } +func isRangeCheck(v ssa.Value, x ssa.Value) bool { + compareVal, _ := getRealValueFromOperation(x) + switch op := v.(type) { + case *ssa.BinOp: + switch op.Op { + case token.LSS, token.LEQ, token.GTR, token.GEQ, + token.EQL, token.NEQ: + return op.X == compareVal || op.Y == compareVal + } + } return false } + +func getRealValueFromOperation(v ssa.Value) (ssa.Value, string) { + switch v := v.(type) { + case *ssa.UnOp: + if v.Op == token.SUB { + return v.X, "neg" + } + } + return v, "" +} + +func explicitValsInRange(explicitPosVals []uint, explicitNegVals []int, dstInt integer) bool { + if len(explicitPosVals) == 0 && len(explicitNegVals) == 0 { + return false + } + + for _, val := range explicitPosVals { + if val > dstInt.max { + return false + } + } + + for _, val := range explicitNegVals { + if val < dstInt.min { + return false + } + } + + return true +} + +func min[T constraints.Integer](a T, b *T) T { + if b == nil { + return a + } + if a < *b { + return a + } + return *b +} + +func max[T constraints.Integer](a T, b *T) T { + if b == nil { + return a + } + if a > *b { + return a + } + return *b +} + +func toPtr[T any](a T) *T { + return &a +} diff --git a/analyzers/conversion_overflow_test.go b/analyzers/conversion_overflow_test.go new file mode 100644 index 0000000..8a84837 --- /dev/null +++ b/analyzers/conversion_overflow_test.go @@ -0,0 +1,139 @@ +package analyzers + +import ( + "math" + "strconv" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ParseIntType", func() { + Context("with valid input", func() { + DescribeTable("should correctly parse and calculate bounds for", + func(intType string, expectedSigned bool, expectedSize int, expectedMin int, expectedMax uint) { + result, err := parseIntType(intType) + Expect(err).NotTo(HaveOccurred()) + Expect(result.signed).To(Equal(expectedSigned)) + Expect(result.size).To(Equal(expectedSize)) + Expect(result.min).To(Equal(expectedMin)) + Expect(result.max).To(Equal(expectedMax)) + }, + Entry("uint8", "uint8", false, 8, 0, uint(math.MaxUint8)), + Entry("int8", "int8", true, 8, math.MinInt8, uint(math.MaxInt8)), + Entry("uint16", "uint16", false, 16, 0, uint(math.MaxUint16)), + Entry("int16", "int16", true, 16, math.MinInt16, uint(math.MaxInt16)), + Entry("uint32", "uint32", false, 32, 0, uint(math.MaxUint32)), + Entry("int32", "int32", true, 32, math.MinInt32, uint(math.MaxInt32)), + Entry("uint64", "uint64", false, 64, 0, uint(math.MaxUint64)), + Entry("int64", "int64", true, 64, math.MinInt64, uint(math.MaxInt64)), + ) + + It("should use system's int size for 'int' and 'uint'", func() { + intResult, err := parseIntType("int") + Expect(err).NotTo(HaveOccurred()) + Expect(intResult.size).To(Equal(strconv.IntSize)) + + uintResult, err := parseIntType("uint") + Expect(err).NotTo(HaveOccurred()) + Expect(uintResult.size).To(Equal(strconv.IntSize)) + }) + }) + + Context("with invalid input", func() { + DescribeTable("should return an error for", + func(intType string, expectedErrorString string) { + _, err := parseIntType(intType) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(expectedErrorString)) + }, + Entry("empty string", "", "no integer type match found for "), + Entry("invalid type", "float64", "no integer type match found for float64"), + Entry("invalid size", "int65", "invalid bit size: 65"), + Entry("negative size", "int-8", "no integer type match found for int-8"), + Entry("non-numeric size", "intABC", "no integer type match found for intABC"), + ) + }) +}) + +var _ = Describe("IsIntOverflow", func() { + DescribeTable("should correctly identify overflow scenarios on a 64-bit system", + func(src string, dst string, expectedOverflow bool) { + result := isIntOverflow(src, dst) + Expect(result).To(Equal(expectedOverflow)) + }, + // Unsigned to Signed conversions + Entry("uint8 to int8", "uint8", "int8", true), + Entry("uint8 to int16", "uint8", "int16", false), + Entry("uint8 to int32", "uint8", "int32", false), + Entry("uint8 to int64", "uint8", "int64", false), + Entry("uint16 to int8", "uint16", "int8", true), + Entry("uint16 to int16", "uint16", "int16", true), + Entry("uint16 to int32", "uint16", "int32", false), + Entry("uint16 to int64", "uint16", "int64", false), + Entry("uint32 to int8", "uint32", "int8", true), + Entry("uint32 to int16", "uint32", "int16", true), + Entry("uint32 to int32", "uint32", "int32", true), + Entry("uint32 to int64", "uint32", "int64", false), + Entry("uint64 to int8", "uint64", "int8", true), + Entry("uint64 to int16", "uint64", "int16", true), + Entry("uint64 to int32", "uint64", "int32", true), + Entry("uint64 to int64", "uint64", "int64", true), + + // Unsigned to Unsigned conversions + Entry("uint8 to uint16", "uint8", "uint16", false), + Entry("uint8 to uint32", "uint8", "uint32", false), + Entry("uint8 to uint64", "uint8", "uint64", false), + Entry("uint16 to uint8", "uint16", "uint8", true), + Entry("uint16 to uint32", "uint16", "uint32", false), + Entry("uint16 to uint64", "uint16", "uint64", false), + Entry("uint32 to uint8", "uint32", "uint8", true), + Entry("uint32 to uint16", "uint32", "uint16", true), + Entry("uint32 to uint64", "uint32", "uint64", false), + Entry("uint64 to uint8", "uint64", "uint8", true), + Entry("uint64 to uint16", "uint64", "uint16", true), + Entry("uint64 to uint32", "uint64", "uint32", true), + + // Signed to Unsigned conversions + Entry("int8 to uint8", "int8", "uint8", true), + Entry("int8 to uint16", "int8", "uint16", true), + Entry("int8 to uint32", "int8", "uint32", true), + Entry("int8 to uint64", "int8", "uint64", true), + Entry("int16 to uint8", "int16", "uint8", true), + Entry("int16 to uint16", "int16", "uint16", true), + Entry("int16 to uint32", "int16", "uint32", true), + Entry("int16 to uint64", "int16", "uint64", true), + Entry("int32 to uint8", "int32", "uint8", true), + Entry("int32 to uint16", "int32", "uint16", true), + Entry("int32 to uint32", "int32", "uint32", true), + Entry("int32 to uint64", "int32", "uint64", true), + Entry("int64 to uint8", "int64", "uint8", true), + Entry("int64 to uint16", "int64", "uint16", true), + Entry("int64 to uint32", "int64", "uint32", true), + Entry("int64 to uint64", "int64", "uint64", true), + + // Signed to Signed conversions + Entry("int8 to int16", "int8", "int16", false), + Entry("int8 to int32", "int8", "int32", false), + Entry("int8 to int64", "int8", "int64", false), + Entry("int16 to int8", "int16", "int8", true), + Entry("int16 to int32", "int16", "int32", false), + Entry("int16 to int64", "int16", "int64", false), + Entry("int32 to int8", "int32", "int8", true), + Entry("int32 to int16", "int32", "int16", true), + Entry("int32 to int64", "int32", "int64", false), + Entry("int64 to int8", "int64", "int8", true), + Entry("int64 to int16", "int64", "int16", true), + Entry("int64 to int32", "int64", "int32", true), + + // Same type conversions (should never overflow) + Entry("uint8 to uint8", "uint8", "uint8", false), + Entry("uint16 to uint16", "uint16", "uint16", false), + Entry("uint32 to uint32", "uint32", "uint32", false), + Entry("uint64 to uint64", "uint64", "uint64", false), + Entry("int8 to int8", "int8", "int8", false), + Entry("int16 to int16", "int16", "int16", false), + Entry("int32 to int32", "int32", "int32", false), + Entry("int64 to int64", "int64", "int64", false), + ) +}) diff --git a/go.mod b/go.mod index a6db757..6675ea4 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/onsi/gomega v1.34.2 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 golang.org/x/text v0.17.0 golang.org/x/tools v0.24.0 diff --git a/go.sum b/go.sum index b4740e1..88328d1 100644 --- a/go.sum +++ b/go.sum @@ -428,6 +428,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/testutils/g115_samples.go b/testutils/g115_samples.go index 9d264d8..62d4993 100644 --- a/testutils/g115_samples.go +++ b/testutils/g115_samples.go @@ -287,10 +287,54 @@ package main import ( "fmt" "math" + "math/rand" ) func main() { - var a int64 = 13 + a := rand.Int63() + if a < math.MinInt32 { + panic("out of range") + } + if a > math.MaxInt32 { + panic("out of range") + } + b := int32(a) + fmt.Printf("%d\n", b) +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if a < math.MinInt32 && a > math.MaxInt32 { + panic("out of range") + } + b := int32(a) + fmt.Printf("%d\n", b) +} + `, + }, 1, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() if a < math.MinInt32 || a > math.MaxInt32 { panic("out of range") } @@ -330,7 +374,7 @@ import ( func main() { var a int32 = math.MaxInt32 - if a < math.MinInt32 || a > math.MaxInt32 { + if a < math.MinInt32 && a > math.MaxInt32 { panic("out of range") } var b int64 = int64(a) * 2 @@ -390,4 +434,286 @@ func main() { } `, }, 1, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if a < 0 { + panic("out of range") + } + if a > math.MaxUint32 { + panic("out of range") + } + b := uint32(a) + fmt.Printf("%d\n", b) +} +`, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + a := rand.Int63() + if a < 0 { + panic("out of range") + } + b := uint32(a) + fmt.Printf("%d\n", b) +} +`, + }, 1, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "math" +) + +func foo(x int) uint32 { + if x < 0 { + return 0 + } + if x > math.MaxUint32 { + return math.MaxUint32 + } + return uint32(x) +} +`, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "math" +) + +func foo(items []string) uint32 { + x := len(items) + if x > math.MaxUint32 { + return math.MaxUint32 + } + return uint32(x) +} +`, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "math" +) + +func foo(items []string) uint32 { + x := cap(items) + if x > math.MaxUint32 { + return math.MaxUint32 + } + return uint32(x) +} +`, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "math" +) + +func foo(items []string) uint32 { + x := len(items) + if x < math.MaxUint32 { + return uint32(x) + } + return math.MaxUint32 +} +`, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if a >= math.MinInt32 && a <= math.MaxInt32 { + b := int32(a) + fmt.Printf("%d\n", b) + } + panic("out of range") +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if a >= math.MinInt32 && a <= math.MaxInt32 { + b := int32(a) + fmt.Printf("%d\n", b) + } + panic("out of range") +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if !(a >= math.MinInt32) && a > math.MaxInt32 { + b := int32(a) + fmt.Printf("%d\n", b) + } + panic("out of range") +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if !(a >= math.MinInt32) || a > math.MaxInt32 { + panic("out of range") + } + b := int32(a) + fmt.Printf("%d\n", b) +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math" + "math/rand" +) + +func main() { + a := rand.Int63() + if math.MinInt32 <= a && math.MaxInt32 >= a { + b := int32(a) + fmt.Printf("%d\n", b) + } + panic("out of range") +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + a := rand.Int63() + if a == 3 || a == 4 { + b := int32(a) + fmt.Printf("%d\n", b) + } + panic("out of range") +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import ( + "fmt" + "math/rand" +) + +func main() { + a := rand.Int63() + if a != 3 || a != 4 { + panic("out of range") + } + b := int32(a) + fmt.Printf("%d\n", b) +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` +package main + +import "unsafe" + +func main() { + i := uintptr(123) + p := unsafe.Pointer(i) + _ = p +} + `, + }, 0, gosec.NewConfig()}, + {[]string{ + ` + package main + + import ( + "fmt" + "math/rand" + ) + + func main() { + a := rand.Int63() + if a >= 0 { + panic("no positivity allowed") + } + b := uint64(-a) + fmt.Printf("%d\n", b) + } + `, + }, 0, gosec.NewConfig()}, }