Skip to content

Commit 878f149

Browse files
authored
[safecast] Introduced utilities to perform casting safely (#511)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description protect against [CWE-190](https://cwe.mitre.org/data/definitions/190.html) ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent 87ce671 commit 878f149

File tree

15 files changed

+897
-24
lines changed

15 files changed

+897
-24
lines changed

changes/20241107160700.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[safecast]` Introduced utilities to perform casting safely and protect against [CWE-190](https://cwe.mitre.org/data/definitions/190.html)

utils/field/fields_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010

1111
"github.com/go-faker/faker/v4"
1212
"github.com/stretchr/testify/assert"
13+
14+
"github.com/ARM-software/golang-utils/utils/safecast"
1315
)
1416

1517
func TestOptionalField(t *testing.T) {
@@ -22,7 +24,7 @@ func TestOptionalField(t *testing.T) {
2224
}{
2325
{
2426
fieldType: "Int",
25-
value: time.Now().Second(),
27+
value: safecast.ToInt(time.Now().Second()),
2628
defaultValue: 76,
2729
setFunction: func(a any) any {
2830
return ToOptionalInt(a.(int))
@@ -37,8 +39,8 @@ func TestOptionalField(t *testing.T) {
3739
},
3840
{
3941
fieldType: "UInt",
40-
value: uint(time.Now().Second()), //nolint:gosec // time is positive and uint has more bits than int so no overflow
41-
defaultValue: uint(76),
42+
value: safecast.ToUint(time.Now().Second()),
43+
defaultValue: safecast.ToUint(76),
4244
setFunction: func(a any) any {
4345
return ToOptionalUint(a.(uint))
4446
},
@@ -52,8 +54,8 @@ func TestOptionalField(t *testing.T) {
5254
},
5355
{
5456
fieldType: "Int32",
55-
value: int32(time.Now().Second()), //nolint:gosec // this should be okay until 2038
56-
defaultValue: int32(97894),
57+
value: safecast.ToInt32(time.Now().Second()),
58+
defaultValue: safecast.ToInt32(97894),
5759
setFunction: func(a any) any {
5860
return ToOptionalInt32(a.(int32))
5961
},
@@ -67,8 +69,8 @@ func TestOptionalField(t *testing.T) {
6769
},
6870
{
6971
fieldType: "UInt32",
70-
value: uint32(time.Now().Second()), //nolint:gosec // this should be okay until 2038
71-
defaultValue: uint32(97894),
72+
value: safecast.ToUint32(time.Now().Second()),
73+
defaultValue: safecast.ToUint32(97894),
7274
setFunction: func(a any) any {
7375
return ToOptionalUint32(a.(uint32))
7476
},
@@ -83,7 +85,7 @@ func TestOptionalField(t *testing.T) {
8385
{
8486
fieldType: "Int64",
8587
value: time.Now().Unix(),
86-
defaultValue: int64(97894),
88+
defaultValue: safecast.ToInt64(97894),
8789
setFunction: func(a any) any {
8890
return ToOptionalInt64(a.(int64))
8991
},
@@ -97,8 +99,8 @@ func TestOptionalField(t *testing.T) {
9799
},
98100
{
99101
fieldType: "UInt64",
100-
value: uint64(time.Now().Unix()), //nolint:gosec // time is positive and uint64 has more bits than int64 so no overflow
101-
defaultValue: uint64(97894),
102+
value: safecast.ToUint64(time.Now().Unix()),
103+
defaultValue: safecast.ToUint64(97894),
102104
setFunction: func(a any) any {
103105
return ToOptionalUint64(a.(uint64))
104106
},

utils/filesystem/zip.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/ARM-software/golang-utils/utils/collection"
1919
"github.com/ARM-software/golang-utils/utils/commonerrors"
2020
"github.com/ARM-software/golang-utils/utils/parallelisation"
21+
"github.com/ARM-software/golang-utils/utils/safecast"
2122
"github.com/ARM-software/golang-utils/utils/safeio"
2223
)
2324

@@ -356,16 +357,16 @@ func (fs *VFS) unzip(ctx context.Context, source string, destination string, lim
356357
fileCounter.Inc()
357358
fileList = append(fileList, filePath)
358359
}
359-
totalSizeOnDisk.Add(uint64(fileSizeOnDisk)) //nolint:gosec // file size is positive and uint64 has more bits than int64 so no overflow
360+
totalSizeOnDisk.Add(safecast.ToUint64(fileSizeOnDisk))
360361
}
361362
} else {
362-
totalSizeOnDisk.Add(uint64(fileSizeOnDisk)) //nolint:gosec // file size is positive and uint64 has more bits than int64 so no overflow
363+
totalSizeOnDisk.Add(safecast.ToUint64(fileSizeOnDisk))
363364
}
364365

365366
if limits.Apply() && totalSizeOnDisk.Load() > limits.GetMaxTotalSize() {
366367
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())
367368
}
368-
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
369+
if filecount := fileCounter.Load(); limits.Apply() && filecount <= math.MaxInt64 && safecast.ToInt64(filecount) > limits.GetMaxFileCount() {
369370
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)
370371
}
371372
}

utils/idgen/uuid.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
*/
55
package idgen
66

7-
import "github.com/gofrs/uuid"
7+
import (
8+
"fmt"
9+
10+
"github.com/gofrs/uuid"
11+
12+
"github.com/ARM-software/golang-utils/utils/commonerrors"
13+
)
814

915
// Generates a UUID.
1016
func GenerateUUID4() (string, error) {
1117
uuid, err := uuid.NewV4()
1218
if err != nil {
13-
return "", err
19+
return "", fmt.Errorf("%w: failed generating uuid: %v", commonerrors.ErrUnexpected, err.Error())
1420
}
1521
return uuid.String(), nil
1622
}

utils/idgen/uuid_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ import (
1313

1414
func TestUuidUniqueness(t *testing.T) {
1515
uuid1, err := GenerateUUID4()
16-
require.Nil(t, err)
16+
require.NoError(t, err)
1717

1818
uuid2, err := GenerateUUID4()
19-
require.Nil(t, err)
19+
require.NoError(t, err)
2020

2121
assert.NotEqual(t, uuid1, uuid2)
2222
}
2323

2424
func TestUuidLength(t *testing.T) {
2525
uuid, err := GenerateUUID4()
26-
require.Nil(t, err)
26+
require.NoError(t, err)
2727

2828
assert.Equal(t, 36, len(uuid))
2929
}

utils/platform/os.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/shirou/gopsutil/v3/mem"
2020

2121
"github.com/ARM-software/golang-utils/utils/commonerrors"
22+
"github.com/ARM-software/golang-utils/utils/safecast"
2223
)
2324

2425
var (
@@ -114,7 +115,7 @@ func UpTime() (uptime time.Duration, err error) {
114115
err = fmt.Errorf("%w: could not convert uptime '%v' to duration as it exceeds the upper limit for time.Duration", commonerrors.ErrOutOfRange, _uptime)
115116
return
116117
}
117-
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
118+
uptime = time.Duration(safecast.ToInt64(_uptime)) * time.Second
118119
return
119120
}
120121

@@ -128,7 +129,7 @@ func BootTime() (bootime time.Time, err error) {
128129
err = fmt.Errorf("%w: could not convert uptime '%v' to duration as it exceeds the upper limit for time.Duration", commonerrors.ErrOutOfRange, _bootime)
129130
return
130131
}
131-
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
132+
bootime = time.Unix(safecast.ToInt64(_bootime), 0)
132133
return
133134

134135
}

utils/proc/process.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/ARM-software/golang-utils/utils/collection"
1515
"github.com/ARM-software/golang-utils/utils/commonerrors"
1616
"github.com/ARM-software/golang-utils/utils/parallelisation"
17+
"github.com/ARM-software/golang-utils/utils/safecast"
1718
)
1819

1920
const (
@@ -245,7 +246,7 @@ func isProcessRunning(p *process.Process) (running bool) {
245246
// to get more information about the process. An error will be returned
246247
// if the process does not exist.
247248
func NewProcess(ctx context.Context, pid int) (pr IProcess, err error) {
248-
p, err := process.NewProcessWithContext(ctx, int32(pid)) //nolint:gosec // Max PID is 2^22 which is within int32 range https://stackoverflow.com/a/6294196
249+
p, err := process.NewProcessWithContext(ctx, safecast.ToInt32(pid))
249250
err = ConvertProcessError(err)
250251
if err != nil {
251252
return

utils/reflection/reflection.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func GetStructureField(field reflect.Value) interface{} {
2020
if !field.IsValid() {
2121
return nil
2222
}
23-
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
23+
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
2424
Elem().
2525
Interface()
2626
}
@@ -31,7 +31,7 @@ func SetStructureField(field reflect.Value, value interface{}) {
3131
if !field.IsValid() {
3232
return
3333
}
34-
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
34+
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
3535
Elem().
3636
Set(reflect.ValueOf(value))
3737
}

utils/retry/retry.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/go-logr/logr"
1010

1111
"github.com/ARM-software/golang-utils/utils/commonerrors"
12+
"github.com/ARM-software/golang-utils/utils/safecast"
1213
)
1314

1415
// RetryIf will retry fn when the value returned from retryConditionFn is true
@@ -39,7 +40,7 @@ func RetryIf(ctx context.Context, logger logr.Logger, retryPolicy *RetryPolicyCo
3940
retry.MaxDelay(retryPolicy.RetryWaitMax),
4041
retry.MaxJitter(25*time.Millisecond),
4142
retry.DelayType(retryType),
42-
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
43+
retry.Attempts(safecast.ToUint(retryPolicy.RetryMax)),
4344
retry.RetryIf(retryConditionFn),
4445
retry.LastErrorOnly(true),
4546
retry.Context(ctx),

utils/safecast/ReadMe.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Safecast
2+
3+
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.
4+
It should help tackling gosec [G115 rule](https://github.com/securego/gosec/pull/1149)
5+
6+
G115: Potential overflow when converting between integer types.
7+
8+
and [CWE-190](https://cwe.mitre.org/data/definitions/190.html)
9+
10+
11+
infinite loop
12+
access to wrong resource by id
13+
grant access to someone who exhausted their quota
14+
15+
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.
16+
For instance, `toInt8(255)-> 127` and `toInt8(-255)-> -128`
17+
18+
19+

0 commit comments

Comments
 (0)