Improvement the int conversion overflow logic to handle bound checks (#1194)

* add test cases

Signed-off-by: czechbol <adamludes@gmail.com>

* fix bounds check logic

Signed-off-by: czechbol <adamludes@gmail.com>

* tweak test cases

Signed-off-by: czechbol <adamludes@gmail.com>

* fix codestyle

Signed-off-by: czechbol <adamludes@gmail.com>

* improve bounds check logic

Signed-off-by: czechbol <adamludes@gmail.com>

* max recursion depth

Signed-off-by: czechbol <adamludes@gmail.com>

* add test case for len function

Signed-off-by: czechbol <adamludes@gmail.com>

* relax len function bounds checks

Co-authored-by: Ben Krieger <ben.krieger@intel.com>

* handle cases when convert instruction is after the if blocks

Signed-off-by: czechbol <adamludes@gmail.com>

* improve range check discovery, add tests

Signed-off-by: czechbol <adamludes@gmail.com>

* refactor for readability

Signed-off-by: czechbol <adamludes@gmail.com>

* add cap function test

Signed-off-by: czechbol <adamludes@gmail.com>

* calculate signed min without throwing overflow warnings

Signed-off-by: czechbol <adamludes@gmail.com>

* perform bounds checks int size calculations

Signed-off-by: czechbol <adamludes@gmail.com>

* basic equal operator logic

Signed-off-by: czechbol <adamludes@gmail.com>

* uintptr -> unsafe.Pointer test case

Signed-off-by: czechbol <adamludes@gmail.com>

* fix review comments

Signed-off-by: czechbol <adamludes@gmail.com>

* Rebase and fix go module

Change-Id: I8da6495eaaf25b1739389aa98492bd7df338085b
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>

* fix false positive for negated value

Signed-off-by: czechbol <adamludes@gmail.com>

* fix range conditions

Signed-off-by: czechbol <adamludes@gmail.com>

* Ignore the golangci/gosec G115 warning

Change-Id: I0db56cb0a5f9ab6e815e2480ec0b66d7061b23d3
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>

---------

Signed-off-by: czechbol <adamludes@gmail.com>
Signed-off-by: Cosmin Cojocar <ccojocar@google.com>
Co-authored-by: Ben Krieger <ben.krieger@intel.com>
Co-authored-by: Cosmin Cojocar <ccojocar@google.com>
This commit is contained in:
czechbol 2024-09-04 16:09:54 +02:00 committed by GitHub
parent ea5b2766bb
commit eaedce9a8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 838 additions and 71 deletions

View file

@ -17,9 +17,12 @@ package analyzers
import ( import (
"fmt" "fmt"
"go/token" "go/token"
"math"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"golang.org/x/exp/constraints"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
@ -27,6 +30,30 @@ import (
"github.com/securego/gosec/v2/issue" "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 { func newConversionOverflowAnalyzer(id string, description string) *analysis.Analyzer {
return &analysis.Analyzer{ return &analysis.Analyzer{
Name: id, Name: id,
@ -50,10 +77,10 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) {
case *ssa.Convert: case *ssa.Convert:
src := instr.X.Type().Underlying().String() src := instr.X.Type().Underlying().String()
dst := instr.Type().Underlying().String() dst := instr.Type().Underlying().String()
if isIntOverflow(src, dst) {
if isSafeConversion(instr) { if isSafeConversion(instr) {
continue continue
} }
if isIntOverflow(src, dst) {
issue := newIssue(pass.Analyzer.Name, issue := newIssue(pass.Analyzer.Name,
fmt.Sprintf("integer overflow conversion %s -> %s", src, dst), fmt.Sprintf("integer overflow conversion %s -> %s", src, dst),
pass.Fset, pass.Fset,
@ -74,23 +101,90 @@ func runConversionOverflow(pass *analysis.Pass) (interface{}, error) {
return nil, nil 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(`^(?P<type>u?int)(?P<size>\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 { func isSafeConversion(instr *ssa.Convert) bool {
dstType := instr.Type().Underlying().String() dstType := instr.Type().Underlying().String()
// Check for constant conversions // Check for constant conversions.
if constVal, ok := instr.X.(*ssa.Const); ok { if constVal, ok := instr.X.(*ssa.Const); ok {
if isConstantInRange(constVal, dstType) { if isConstantInRange(constVal, dstType) {
return true return true
} }
} }
// Check for explicit range checks // Check for string to integer conversions with specified bit size.
if hasExplicitRangeCheck(instr) { if isStringToIntConversion(instr, dstType) {
return true return true
} }
// Check for string to integer conversions with specified bit size // Check for explicit range checks.
if isStringToIntConversion(instr, dstType) { if hasExplicitRangeCheck(instr, dstType) {
return true return true
} }
@ -114,22 +208,8 @@ func isConstantInRange(constVal *ssa.Const, dstType string) bool {
return value >= 0 && value <= (1<<dstInt.size)-1 return value >= 0 && value <= (1<<dstInt.size)-1
} }
func hasExplicitRangeCheck(instr *ssa.Convert) bool {
block := instr.Block()
for _, i := range block.Instrs {
if binOp, ok := i.(*ssa.BinOp); ok {
// Check if either operand of the BinOp is the result of the Convert instruction
if (binOp.X == instr || binOp.Y == instr) &&
(binOp.Op == token.LSS || binOp.Op == token.LEQ || binOp.Op == token.GTR || binOp.Op == token.GEQ) {
return true
}
}
}
return false
}
func isStringToIntConversion(instr *ssa.Convert, dstType string) bool { func isStringToIntConversion(instr *ssa.Convert, dstType string) bool {
// Traverse the SSA instructions to find the original variable // Traverse the SSA instructions to find the original variable.
original := instr.X original := instr.X
for { for {
switch v := original.(type) { switch v := original.(type) {
@ -162,66 +242,285 @@ func isStringToIntConversion(instr *ssa.Convert, dstType string) bool {
} }
} }
type integer struct { func hasExplicitRangeCheck(instr *ssa.Convert, dstType string) bool {
signed bool dstInt, err := parseIntType(dstType)
size int
}
func parseIntType(intType string) (integer, error) {
re := regexp.MustCompile(`(?P<type>u?int)(?P<size>\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 := false
if it == "int" {
signed = 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)
}
}
return integer{signed: signed, size: intSize}, nil
}
func isIntOverflow(src string, dst string) bool {
srcInt, err := parseIntType(src)
if err != nil { if err != nil {
return false return false
} }
dstInt, err := parseIntType(dst) srcInt, err := parseIntType(instr.X.Type().String())
if err != nil { if err != nil {
return false return false
} }
// converting uint to int of the same size or smaller might lead to overflow minValue := srcInt.min
if !srcInt.signed && dstInt.signed && dstInt.size <= srcInt.size { maxValue := srcInt.max
return true explicitPositiveVals := []uint{}
} explicitNegativeVals := []int{}
// converting uint to unit of a smaller size might lead to overflow
if !srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size { if minValue > dstInt.min && maxValue < dstInt.max {
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 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 return false
} }
// 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}
}
result := rangeResult{
minValue: math.MinInt,
maxValue: math.MaxUint,
isRangeCheck: true,
}
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()))
}
}
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 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
}
}
}
return bounds
}
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
}

View file

@ -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),
)
})

1
go.mod
View file

@ -11,6 +11,7 @@ require (
github.com/onsi/gomega v1.34.2 github.com/onsi/gomega v1.34.2
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.26.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/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/text v0.17.0 golang.org/x/text v0.17.0
golang.org/x/tools v0.24.0 golang.org/x/tools v0.24.0

2
go.sum
View file

@ -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-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-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-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-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/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= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View file

@ -287,10 +287,54 @@ package main
import ( import (
"fmt" "fmt"
"math" "math"
"math/rand"
) )
func main() { 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 { if a < math.MinInt32 || a > math.MaxInt32 {
panic("out of range") panic("out of range")
} }
@ -330,7 +374,7 @@ import (
func main() { func main() {
var a int32 = math.MaxInt32 var a int32 = math.MaxInt32
if a < math.MinInt32 || a > math.MaxInt32 { if a < math.MinInt32 && a > math.MaxInt32 {
panic("out of range") panic("out of range")
} }
var b int64 = int64(a) * 2 var b int64 = int64(a) * 2
@ -390,4 +434,286 @@ func main() {
} }
`, `,
}, 1, gosec.NewConfig()}, }, 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()},
} }