Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/20241107160700.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: `[safecast]` Introduced utilities to perform casting safely and protect against [CWE-190](https://cwe.mitre.org/data/definitions/190.html)
22 changes: 12 additions & 10 deletions utils/field/fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (

"github.com/go-faker/faker/v4"
"github.com/stretchr/testify/assert"

"github.com/ARM-software/golang-utils/utils/safecast"
)

func TestOptionalField(t *testing.T) {
Expand All @@ -22,7 +24,7 @@ func TestOptionalField(t *testing.T) {
}{
{
fieldType: "Int",
value: time.Now().Second(),
value: safecast.ToInt(time.Now().Second()),
defaultValue: 76,
setFunction: func(a any) any {
return ToOptionalInt(a.(int))
Expand All @@ -37,8 +39,8 @@ func TestOptionalField(t *testing.T) {
},
{
fieldType: "UInt",
value: uint(time.Now().Second()), //nolint:gosec // time is positive and uint has more bits than int so no overflow
defaultValue: uint(76),
value: safecast.ToUint(time.Now().Second()),
defaultValue: safecast.ToUint(76),
setFunction: func(a any) any {
return ToOptionalUint(a.(uint))
},
Expand All @@ -52,8 +54,8 @@ func TestOptionalField(t *testing.T) {
},
{
fieldType: "Int32",
value: int32(time.Now().Second()), //nolint:gosec // this should be okay until 2038
defaultValue: int32(97894),
value: safecast.ToInt32(time.Now().Second()),
defaultValue: safecast.ToInt32(97894),
setFunction: func(a any) any {
return ToOptionalInt32(a.(int32))
},
Expand All @@ -67,8 +69,8 @@ func TestOptionalField(t *testing.T) {
},
{
fieldType: "UInt32",
value: uint32(time.Now().Second()), //nolint:gosec // this should be okay until 2038
defaultValue: uint32(97894),
value: safecast.ToUint32(time.Now().Second()),
defaultValue: safecast.ToUint32(97894),
setFunction: func(a any) any {
return ToOptionalUint32(a.(uint32))
},
Expand All @@ -83,7 +85,7 @@ func TestOptionalField(t *testing.T) {
{
fieldType: "Int64",
value: time.Now().Unix(),
defaultValue: int64(97894),
defaultValue: safecast.ToInt64(97894),
setFunction: func(a any) any {
return ToOptionalInt64(a.(int64))
},
Expand All @@ -97,8 +99,8 @@ func TestOptionalField(t *testing.T) {
},
{
fieldType: "UInt64",
value: uint64(time.Now().Unix()), //nolint:gosec // time is positive and uint64 has more bits than int64 so no overflow
defaultValue: uint64(97894),
value: safecast.ToUint64(time.Now().Unix()),
defaultValue: safecast.ToUint64(97894),
setFunction: func(a any) any {
return ToOptionalUint64(a.(uint64))
},
Expand Down
7 changes: 4 additions & 3 deletions utils/filesystem/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ARM-software/golang-utils/utils/collection"
"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/parallelisation"
"github.com/ARM-software/golang-utils/utils/safecast"
"github.com/ARM-software/golang-utils/utils/safeio"
)

Expand Down Expand Up @@ -356,16 +357,16 @@ func (fs *VFS) unzip(ctx context.Context, source string, destination string, lim
fileCounter.Inc()
fileList = append(fileList, filePath)
}
totalSizeOnDisk.Add(uint64(fileSizeOnDisk)) //nolint:gosec // file size is positive and uint64 has more bits than int64 so no overflow
totalSizeOnDisk.Add(safecast.ToUint64(fileSizeOnDisk))
}
} else {
totalSizeOnDisk.Add(uint64(fileSizeOnDisk)) //nolint:gosec // file size is positive and uint64 has more bits than int64 so no overflow
totalSizeOnDisk.Add(safecast.ToUint64(fileSizeOnDisk))
}

if limits.Apply() && totalSizeOnDisk.Load() > limits.GetMaxTotalSize() {
return fileList, fileCounter.Load(), totalSizeOnDisk.Load(), fmt.Errorf("%w: more than %v B of disk space was used while unzipping %v (%v B used already)", commonerrors.ErrTooLarge, limits.GetMaxTotalSize(), source, totalSizeOnDisk.Load())
}
if filecount := fileCounter.Load(); limits.Apply() && filecount <= math.MaxInt64 && int64(filecount) > limits.GetMaxFileCount() { //nolint:gosec // if filecount of uint64 is greater than the max value of int64 then it must be greater than GetMaxFileCount as that is an int64
if filecount := fileCounter.Load(); limits.Apply() && filecount <= math.MaxInt64 && safecast.ToInt64(filecount) > limits.GetMaxFileCount() {
return fileList, filecount, totalSizeOnDisk.Load(), fmt.Errorf("%w: more than %v files were created while unzipping %v (%v files created already)", commonerrors.ErrTooLarge, limits.GetMaxFileCount(), source, filecount)
}
}
Expand Down
10 changes: 8 additions & 2 deletions utils/idgen/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
*/
package idgen

import "github.com/gofrs/uuid"
import (
"fmt"

"github.com/gofrs/uuid"

"github.com/ARM-software/golang-utils/utils/commonerrors"
)

// Generates a UUID.
func GenerateUUID4() (string, error) {
uuid, err := uuid.NewV4()
if err != nil {
return "", err
return "", fmt.Errorf("%w: failed generating uuid: %v", commonerrors.ErrUnexpected, err.Error())
}
return uuid.String(), nil
}
Expand Down
6 changes: 3 additions & 3 deletions utils/idgen/uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import (

func TestUuidUniqueness(t *testing.T) {
uuid1, err := GenerateUUID4()
require.Nil(t, err)
require.NoError(t, err)

uuid2, err := GenerateUUID4()
require.Nil(t, err)
require.NoError(t, err)

assert.NotEqual(t, uuid1, uuid2)
}

func TestUuidLength(t *testing.T) {
uuid, err := GenerateUUID4()
require.Nil(t, err)
require.NoError(t, err)

assert.Equal(t, 36, len(uuid))
}
Expand Down
5 changes: 3 additions & 2 deletions utils/platform/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/shirou/gopsutil/v3/mem"

"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/safecast"
)

var (
Expand Down Expand Up @@ -114,7 +115,7 @@ func UpTime() (uptime time.Duration, err error) {
err = fmt.Errorf("%w: could not convert uptime '%v' to duration as it exceeds the upper limit for time.Duration", commonerrors.ErrOutOfRange, _uptime)
return
}
uptime = time.Duration(_uptime) * time.Second //nolint:gosec // we have verified the value of _uptime is whithin the upper limit for time.Duration in the above check
uptime = time.Duration(safecast.ToInt64(_uptime)) * time.Second
return
}

Expand All @@ -128,7 +129,7 @@ func BootTime() (bootime time.Time, err error) {
err = fmt.Errorf("%w: could not convert uptime '%v' to duration as it exceeds the upper limit for time.Duration", commonerrors.ErrOutOfRange, _bootime)
return
}
bootime = time.Unix(int64(_bootime), 0) //nolint:gosec // we have verified the value of _bootime is whithin the upper limit for time.Duration in the above check
bootime = time.Unix(safecast.ToInt64(_bootime), 0)
return

}
Expand Down
3 changes: 2 additions & 1 deletion utils/proc/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/ARM-software/golang-utils/utils/collection"
"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/parallelisation"
"github.com/ARM-software/golang-utils/utils/safecast"
)

const (
Expand Down Expand Up @@ -245,7 +246,7 @@ func isProcessRunning(p *process.Process) (running bool) {
// to get more information about the process. An error will be returned
// if the process does not exist.
func NewProcess(ctx context.Context, pid int) (pr IProcess, err error) {
p, err := process.NewProcessWithContext(ctx, int32(pid)) //nolint:gosec // Max PID is 2^22 which is within int32 range https://stackoverflow.com/a/6294196
p, err := process.NewProcessWithContext(ctx, safecast.ToInt32(pid))
err = ConvertProcessError(err)
if err != nil {
return
Expand Down
4 changes: 2 additions & 2 deletions utils/reflection/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func GetStructureField(field reflect.Value) interface{} {
if !field.IsValid() {
return nil
}
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())). //nolint:gosec // this conversion is is between types recommended by Go https://cs.opensource.google/go/go/+/master:src/reflect/value.go;l=2445
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())). //nolint:gosec // this conversion is between types recommended by Go https://cs.opensource.google/go/go/+/master:src/reflect/value.go;l=2445
Elem().
Interface()
}
Expand All @@ -31,7 +31,7 @@ func SetStructureField(field reflect.Value, value interface{}) {
if !field.IsValid() {
return
}
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())). //nolint:gosec // this conversion is is between types recommended by Go https://cs.opensource.google/go/go/+/master:src/reflect/value.go;l=2445
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())). //nolint:gosec // this conversion is between types recommended by Go https://cs.opensource.google/go/go/+/master:src/reflect/value.go;l=2445
Elem().
Set(reflect.ValueOf(value))
}
Expand Down
3 changes: 2 additions & 1 deletion utils/retry/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/go-logr/logr"

"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/safecast"
)

// RetryIf will retry fn when the value returned from retryConditionFn is true
Expand Down Expand Up @@ -39,7 +40,7 @@ func RetryIf(ctx context.Context, logger logr.Logger, retryPolicy *RetryPolicyCo
retry.MaxDelay(retryPolicy.RetryWaitMax),
retry.MaxJitter(25*time.Millisecond),
retry.DelayType(retryType),
retry.Attempts(uint(retryPolicy.RetryMax)), //nolint:gosec // in normal use this will have had Validate() called which enforces that the minimum number of RetryMax is 0 so it won't overflow
retry.Attempts(safecast.ToUint(retryPolicy.RetryMax)),
retry.RetryIf(retryConditionFn),
retry.LastErrorOnly(true),
retry.Context(ctx),
Expand Down
19 changes: 19 additions & 0 deletions utils/safecast/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Safecast

the purpose of this utilities is to perform safe number conversion in go similarly to [go-safecast](https://github.com/ccoVeille/go-safecast) from which they are inspired from.
It should help tackling gosec [G115 rule](https://github.com/securego/gosec/pull/1149)

G115: Potential overflow when converting between integer types.

and [CWE-190](https://cwe.mitre.org/data/definitions/190.html)


infinite loop
access to wrong resource by id
grant access to someone who exhausted their quota

Contrary to `go-safecast` no error is returned when attempting casting and the MAX or MIN value of the type is returned instead if the value is beyond the allowed window.
For instance, `toInt8(255)-> 127` and `toInt8(-255)-> -128`



35 changes: 35 additions & 0 deletions utils/safecast/boundary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package safecast

func greaterThanUpperBoundary[C1 IConvertable, C2 IConvertable](value C1, upperBoundary C2) (greater bool) {
if value <= 0 {
return
}

switch f := any(value).(type) {
case float64:
greater = f >= float64(upperBoundary)
case float32:
greater = float64(f) >= float64(upperBoundary)
default:
// for all other integer types, it fits in an uint64 without overflow as we know value is positive.
greater = uint64(value) > uint64(upperBoundary)
}

return
}

func lessThanLowerBoundary[T IConvertable, T2 IConvertable](value T, boundary T2) (lower bool) {
if value >= 0 {
return
}

switch f := any(value).(type) {
case float64:
lower = f <= float64(boundary)
case float32:
lower = float64(f) <= float64(boundary)
default:
lower = int64(value) < int64(boundary)
}
return
}
Loading
Loading