Skip to content

Commit 012dd48

Browse files
author
sreya
committed
fix: properly handle integer conversions for Go 1.24 lint
1 parent 90f7e9b commit 012dd48

File tree

10 files changed

+136
-34
lines changed

10 files changed

+136
-34
lines changed

cli/cliutil/levenshtein/levenshtein.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,13 @@ func Distance(a, b string, maxDist int) (int, error) {
7777
d[i][j]+subCost, // substitution
7878
)
7979
// check maxDist on the diagonal
80-
if maxDist > -1 && i == j && maxDist <= 255 && d[i+1][j+1] > uint8(maxDist) { // #nosec G115 -- we check maxDist <= 255
81-
return int(d[i+1][j+1]), ErrMaxDist
80+
if maxDist > -1 && i == j {
81+
if maxDist > 255 {
82+
// If maxDist is too large for uint8, we'll never trigger early exit
83+
// which is fine - it just means we calculate the full distance
84+
} else if d[i+1][j+1] > uint8(maxDist) {
85+
return int(d[i+1][j+1]), ErrMaxDist
86+
}
8287
}
8388
}
8489
}

coderd/database/lock.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ const (
1515
)
1616

1717
// GenLockID generates a unique and consistent lock ID from a given string.
18+
// It ensures the returned value is positive to avoid issues with negative lock IDs.
1819
func GenLockID(name string) int64 {
1920
hash := fnv.New64()
2021
_, _ = hash.Write([]byte(name))
21-
// For our locking purposes, it's acceptable to have potential overflow
22-
// The important part is consistency of the lock ID for a given name
23-
return int64(hash.Sum64()) // #nosec G115 -- potential overflow is acceptable for lock IDs
22+
23+
// Convert to int64 but ensure result is positive by masking off sign bit
24+
// This preserves uniqueness while avoiding overflow problems
25+
return int64(hash.Sum64() & 0x7FFFFFFFFFFFFFFF)
2426
}

coderd/database/modelmethods.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,17 @@ func (t Template) DeepCopy() Template {
158158
// It is more useful to have the days that are allowed to autostart from a UX
159159
// POV. The database prefers the 0 value being 'all days allowed'.
160160
func (t Template) AutostartAllowedDays() uint8 {
161-
// Just flip the binary 0s to 1s and vice versa.
162-
// There is an extra day with the 8th bit that needs to be zeroed.
163-
// The conversion is safe because AutostartBlockDaysOfWeek is enforced to use only the lower 7 bits
164-
return ^uint8(t.AutostartBlockDaysOfWeek) & 0b01111111 // #nosec G115 -- int16 to uint8 is safe as we only use 7 bits
161+
// The days are represented as a bitmap where each bit position corresponds to a day
162+
// We need to:
163+
// 1. Extract only the 7 lower bits that represent the days of the week
164+
// 2. Invert those bits to get the allowed days instead of blocked days
165+
// 3. Ensure the 8th bit is zeroed (we only use 7 bits for 7 days)
166+
167+
// Mask to extract only the lower 7 bits from AutostartBlockDaysOfWeek
168+
daysOfWeek := t.AutostartBlockDaysOfWeek & 0b01111111
169+
170+
// Invert the bits and ensure only 7 bits are used (0-6, representing Monday-Sunday)
171+
return ^uint8(daysOfWeek) & 0b01111111
165172
}
166173

167174
func (TemplateVersion) RBACObject(template Template) rbac.Object {

coderd/schedule/template.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,16 @@ func (r TemplateAutostopRequirement) DaysMap() map[time.Weekday]bool {
7676
// bitmap.
7777
func daysMap(daysOfWeek uint8) map[time.Weekday]bool {
7878
days := make(map[time.Weekday]bool)
79+
80+
// Our bitmap can only represent 7 days (the days of the week)
81+
// and the loop index i can only reach at most 6, so no risk of overflow
7982
for i, day := range DaysOfWeek {
80-
days[day] = daysOfWeek&(1<<uint(i)) != 0 // #nosec G115 -- int to uint is safe for small i values (< 8)
83+
// We know i is in the range [0,6] here since DaysOfWeek has 7 elements
84+
// 1<<i is safe as a uint8 since i < 8
85+
mask := uint8(1) << i
86+
days[day] = (daysOfWeek & mask) != 0
8187
}
88+
8289
return days
8390
}
8491

coderd/telemetry/telemetry.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -723,13 +723,21 @@ func ConvertWorkspace(workspace database.Workspace) Workspace {
723723

724724
// ConvertWorkspaceBuild anonymizes a workspace build.
725725
func ConvertWorkspaceBuild(build database.WorkspaceBuild) WorkspaceBuild {
726+
// BuildNumber conversions should be safe as build numbers are always positive
727+
// and are unlikely to exceed uint32 max value in practice.
728+
// If a negative value is encountered, we'll use 0 as a safe default.
729+
buildNumber := uint32(0)
730+
if build.BuildNumber >= 0 {
731+
buildNumber = uint32(build.BuildNumber)
732+
}
733+
726734
return WorkspaceBuild{
727735
ID: build.ID,
728736
CreatedAt: build.CreatedAt,
729737
WorkspaceID: build.WorkspaceID,
730738
JobID: build.JobID,
731739
TemplateVersionID: build.TemplateVersionID,
732-
BuildNumber: uint32(build.BuildNumber), // #nosec G115 -- int32 to uint32 is safe for build numbers
740+
BuildNumber: buildNumber,
733741
}
734742
}
735743

@@ -1035,9 +1043,11 @@ func ConvertTemplate(dbTemplate database.Template) Template {
10351043
FailureTTLMillis: time.Duration(dbTemplate.FailureTTL).Milliseconds(),
10361044
TimeTilDormantMillis: time.Duration(dbTemplate.TimeTilDormant).Milliseconds(),
10371045
TimeTilDormantAutoDeleteMillis: time.Duration(dbTemplate.TimeTilDormantAutoDelete).Milliseconds(),
1038-
AutostopRequirementDaysOfWeek: codersdk.BitmapToWeekdays(uint8(dbTemplate.AutostopRequirementDaysOfWeek)), // #nosec G115 -- int16 to uint8 is safe since we only use 7 bits
1046+
// Extract only the 7 relevant bits for the days of the week
1047+
AutostopRequirementDaysOfWeek: codersdk.BitmapToWeekdays(uint8(dbTemplate.AutostopRequirementDaysOfWeek & 0b01111111)),
10391048
AutostopRequirementWeeks: dbTemplate.AutostopRequirementWeeks,
1040-
AutostartAllowedDays: codersdk.BitmapToWeekdays(dbTemplate.AutostartAllowedDays()), // #nosec G115 -- uses AutostartAllowedDays() which already ensures safe conversion
1049+
// AutostartAllowedDays() already ensures we only use the 7 relevant bits
1050+
AutostartAllowedDays: codersdk.BitmapToWeekdays(dbTemplate.AutostartAllowedDays(),)
10411051
RequireActiveVersion: dbTemplate.RequireActiveVersion,
10421052
Deprecated: dbTemplate.Deprecated != "",
10431053
}

cryptorand/strings.go

+28-9
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,35 @@ const (
4444
//
4545
//nolint:varnamelen
4646
func unbiasedModulo32(v uint32, n int32) (int32, error) {
47-
prod := uint64(v) * uint64(n) // #nosec G115 -- uint32 to uint64 is always safe
48-
low := uint32(prod) // #nosec G115 -- truncation is intentional for the algorithm
49-
if low < uint32(n) { // #nosec G115 -- int32 to uint32 is safe for positive n (we require n > 0)
50-
thresh := uint32(-n) % uint32(n) // #nosec G115 -- int32 to uint32 after negation is an acceptable pattern here
47+
// Validate n is positive
48+
if n <= 0 {
49+
return 0, xerrors.Errorf("modulus must be positive: %d", n)
50+
}
51+
52+
// These conversions are safe:
53+
// - uint32 to uint64 is always safe (smaller to larger)
54+
// - int32 to uint64 is safe for positive values, which we've validated
55+
prod := uint64(v) * uint64(n)
56+
57+
// Intentional truncation for algorithm
58+
low := uint32(prod)
59+
60+
if low < uint32(n) {
61+
// This conversion is safe since n is positive, so -n as uint32 is well-defined
62+
thresh := uint32(-n) % uint32(n)
63+
5164
for low < thresh {
5265
err := binary.Read(rand.Reader, binary.BigEndian, &v)
5366
if err != nil {
5467
return 0, err
5568
}
56-
prod = uint64(v) * uint64(n) // #nosec G115 -- uint32 to uint64 is always safe
57-
low = uint32(prod) // #nosec G115 -- truncation is intentional for the algorithm
69+
prod = uint64(v) * uint64(n)
70+
low = uint32(prod)
5871
}
5972
}
60-
return int32(prod >> 32), nil // #nosec G115 -- proper range is guaranteed by the algorithm
73+
74+
// The algorithm ensures prod>>32 is always in [0,n), so conversion to int32 is safe
75+
return int32(prod >> 32), nil
6176
}
6277

6378
// StringCharset generates a random string using the provided charset and size.
@@ -87,10 +102,14 @@ func StringCharset(charSetStr string, size int) (string, error) {
87102
r := binary.BigEndian.Uint32(entropy[:4]) // #nosec G115 -- not a conversion, just reading bytes as uint32
88103
entropy = entropy[4:]
89104

90-
// Charset length is limited by string size, so conversion to int32 is safe
105+
// Ensure charset length is within int32 range
106+
charSetLen := len(charSet)
107+
if charSetLen <= 0 || charSetLen > (1<<31)-1 {
108+
return "", xerrors.Errorf("invalid charset length: %d", charSetLen)
109+
}
91110
ci, err := unbiasedModulo32(
92111
r,
93-
int32(len(charSet)), // #nosec G115 -- int to int32 is safe for charset length
112+
int32(charSetLen),
94113
)
95114
if err != nil {
96115
return "", err

provisionerd/runner/runner.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"math"
89
"reflect"
910
"strings"
1011
"sync"
@@ -884,9 +885,36 @@ func (r *Runner) commitQuota(ctx context.Context, resources []*sdkproto.Resource
884885

885886
const stage = "Commit quota"
886887

888+
// Ensure cost doesn't exceed int32 range
889+
var dailyCost int32
890+
if cost > math.MaxInt32 {
891+
// In the unlikely event that cost exceeds int32 range,
892+
// set it to the max value as a reasonable approximation
893+
dailyCost = math.MaxInt32
894+
r.queueLog(ctx, &proto.Log{
895+
Source: proto.LogSource_PROVISIONER,
896+
Level: sdkproto.LogLevel_WARN,
897+
CreatedAt: time.Now().UnixMilli(),
898+
Output: fmt.Sprintf("Cost %d exceeds int32 range, capping at %d", cost, math.MaxInt32),
899+
Stage: stage,
900+
})
901+
} else if cost < 0 {
902+
// Negative costs are invalid; use 0 as a safe value
903+
dailyCost = 0
904+
r.queueLog(ctx, &proto.Log{
905+
Source: proto.LogSource_PROVISIONER,
906+
Level: sdkproto.LogLevel_WARN,
907+
CreatedAt: time.Now().UnixMilli(),
908+
Output: fmt.Sprintf("Negative cost %d is invalid, using 0", cost),
909+
Stage: stage,
910+
})
911+
} else {
912+
dailyCost = int32(cost)
913+
}
914+
887915
resp, err := r.quotaCommitter.CommitQuota(ctx, &proto.CommitQuotaRequest{
888916
JobId: r.job.JobId,
889-
DailyCost: int32(cost), // #nosec G115 -- int to int32 is safe for cost values
917+
DailyCost: dailyCost,
890918
})
891919
if err != nil {
892920
r.queueLog(ctx, &proto.Log{

pty/ssh_other.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package pty
44

55
import (
66
"log"
7+
"math"
78

89
"github.com/gliderlabs/ssh"
910
"github.com/u-root/u-root/pkg/termios"
@@ -90,11 +91,23 @@ func applyTerminalModesToFd(logger *log.Logger, fd uintptr, req ssh.Pty) error {
9091

9192
for c, v := range req.Modes {
9293
if c == gossh.TTY_OP_ISPEED {
93-
tios.Ispeed = int(v) // #nosec G115 -- uint32 to int is safe for TTY speeds
94+
// TTY speeds should always be positive and in a reasonable range
95+
// that fits within int range, but we'll handle edge cases safely.
96+
if v > math.MaxInt {
97+
// If the value is too large, we'll use a large but safe value
98+
tios.Ispeed = math.MaxInt
99+
} else {
100+
tios.Ispeed = int(v)
101+
}
94102
continue
95103
}
96104
if c == gossh.TTY_OP_OSPEED {
97-
tios.Ospeed = int(v) // #nosec G115 -- uint32 to int is safe for TTY speeds
105+
// Same logic as ISPEED
106+
if v > math.MaxInt {
107+
tios.Ospeed = math.MaxInt
108+
} else {
109+
tios.Ospeed = int(v)
110+
}
98111
continue
99112
}
100113
k, ok := terminalModeFlagNames[c]

tailnet/conn.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,13 @@ type TelemetrySink interface {
132132
// NodeID creates a Tailscale NodeID from the last 8 bytes of a UUID. It ensures
133133
// the returned NodeID is always positive.
134134
func NodeID(uid uuid.UUID) tailcfg.NodeID {
135-
// This may overflow, but we handle that by ensuring the result is positive below
136-
id := int64(binary.BigEndian.Uint64(uid[8:])) // #nosec G115 -- potential overflow is handled below
137-
138-
// ensure id is positive
139-
y := id >> 63
140-
id = (id ^ y) - y
141-
142-
return tailcfg.NodeID(id)
135+
// Extract the last 8 bytes of the UUID as a uint64
136+
rawValue := binary.BigEndian.Uint64(uid[8:])
137+
138+
// Ensure we have a positive int64 by masking off the sign bit
139+
// This handles the potential overflow when converting from uint64 to int64
140+
// by ensuring we only use the lower 63 bits
141+
return tailcfg.NodeID(int64(rawValue & 0x7FFFFFFFFFFFFFFF))
143142
}
144143

145144
// NewConn constructs a new Wireguard server that will accept connections from the addresses provided.

tailnet/convert.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tailnet
22

33
import (
4+
"math"
45
"net/netip"
56

67
"github.com/google/uuid"
@@ -29,9 +30,20 @@ func NodeToProto(n *Node) (*proto.Node, error) {
2930
if err != nil {
3031
return nil, err
3132
}
33+
// Create map for the DERP forced websocket settings
3234
derpForcedWebsocket := make(map[int32]string)
35+
36+
// Validate and convert map indices safely
3337
for i, s := range n.DERPForcedWebsocket {
34-
derpForcedWebsocket[int32(i)] = s // #nosec G115 -- int to int32 is safe for indices
38+
// Indices are guaranteed to be small positive values since they're
39+
// array indices, but we'll double-check to be safe
40+
if i < 0 || i > math.MaxInt32 {
41+
// This shouldn't happen in normal operation, but we'll handle it gracefully
42+
// by skipping this entry if it somehow does
43+
continue
44+
}
45+
46+
derpForcedWebsocket[int32(i)] = s
3547
}
3648
addresses := make([]string, len(n.Addresses))
3749
for i, prefix := range n.Addresses {

0 commit comments

Comments
 (0)