Skip to content
Prev Previous commit
Next Next commit
fix(go): lint unsafe env integer narrowing
  • Loading branch information
dvail committed Oct 1, 2026
commit 97e5c1545efc0e91c5f50362a691cf5c675ac896
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
reportSnapshotDS "github.com/stackrox/rox/central/reports/snapshot/datastore"
v1 "github.com/stackrox/rox/generated/api/v1"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/env"
"github.com/stackrox/rox/pkg/errorhelpers"
"github.com/stackrox/rox/pkg/notifier"
"github.com/stackrox/rox/pkg/notifiers"
Expand Down Expand Up @@ -49,7 +48,7 @@ var (
search.NewQuerySelect(search.CVECreatedTime).Proto(),
},
Pagination: search.NewPagination().
Limit(int32(env.ReportMaxRows.IntegerSetting())).
Limit(reportGen.ReportMaxRowsLimit()).
Offset(0).
AddSortOption(search.NewSortOption(search.Cluster)).
AddSortOption(search.NewSortOption(search.Node)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ var (
Schema: selectSchema(),
Selects: getSelectsDeployedImages(),
Pagination: search.NewPagination().
Limit(int32(env.ReportMaxRows.IntegerSetting())).
Limit(ReportMaxRowsLimit()).
Offset(int32(0)).
AddSortOption(search.NewSortOption(search.Cluster)).
AddSortOption(search.NewSortOption(search.Namespace)).Proto(),
Expand All @@ -61,13 +61,20 @@ var (
Schema: selectSchema(),
Selects: getSelectsWatchedImages(),
Pagination: search.NewPagination().
Limit(int32(env.ReportMaxRows.IntegerSetting())).
Limit(ReportMaxRowsLimit()).
Offset(int32(0)).
AddSortOption(search.NewSortOption(search.ImageName)).Proto(),
}
cursorBatchSize = env.PostgresDefaultCursorBatchSize.IntegerSetting()
)

// ReportMaxRowsLimit returns the report max rows setting for query pagination.
func ReportMaxRowsLimit() int32 {
// ReportMaxRows is bounded in pkg/env to make this narrowing conversion safe.
rows := env.ReportMaxRows.IntegerSetting()
return int32(rows)
}

type reportGeneratorImpl struct {
reportSnapshotStore reportSnapshotDS.DataStore
deploymentDatastore deploymentDS.DataStore
Expand Down
7 changes: 5 additions & 2 deletions pkg/env/vulnerability_management.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package env

import "time"
import (
"math"
"time"
)

var (
// ReportExecutionMaxConcurrency sets the maximum number vulnerability reports that can run in parallel
Expand All @@ -20,7 +23,7 @@ var (
OrphanedCVEsRetentionDurationDays = RegisterIntegerSetting("ROX_ORPHANED_CVES_RETENTION_DURATION_DAYS", 2)

// ReportMaxRows sets maximum number of rows for vulnerability reports
ReportMaxRows = RegisterIntegerSetting("ROX_REPORT_MAX_ROWS", 1000000)
ReportMaxRows = RegisterIntegerSetting("ROX_REPORT_MAX_ROWS", 1000000).WithMaximum(math.MaxInt32)

// ReportMissedScheduleRecovery enables recovery of missed scheduled reports on startup
ReportMissedScheduleRecovery = RegisterBooleanSetting("ROX_REPORT_MISSED_SCHEDULE_RECOVERY", true)
Expand Down
8 changes: 5 additions & 3 deletions pkg/grpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
golog "log"
"math"
"net"
"net/http"
"strings"
Expand All @@ -30,15 +31,16 @@ const (
)

var (
maxHTTP2ConcurrentStreamsSetting = env.RegisterIntegerSetting("ROX_HTTP2_MAX_CONCURRENT_STREAMS", defaultMaxHTTP2ConcurrentStreams)
maxHTTP2ConcurrentStreamsSetting = env.RegisterIntegerSetting("ROX_HTTP2_MAX_CONCURRENT_STREAMS", defaultMaxHTTP2ConcurrentStreams).WithMaximum(math.MaxUint32)
)

func maxHTTP2ConcurrentStreams() uint32 {
if maxHTTP2ConcurrentStreamsSetting.IntegerSetting() <= 0 {
maxStreams := maxHTTP2ConcurrentStreamsSetting.IntegerSetting()
if maxStreams <= 0 {
return defaultMaxHTTP2ConcurrentStreams
}

return uint32(maxHTTP2ConcurrentStreamsSetting.IntegerSetting())
return uint32(maxStreams)
}

// EndpointConfig configures an endpoint through which the server is exposed.
Expand Down
8 changes: 5 additions & 3 deletions pkg/grpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package grpc
import (
"context"
"fmt"
"math"
"net"
"net/http"
"strings"
Expand Down Expand Up @@ -60,7 +61,7 @@ var (
log = logging.LoggerForModule()

maxResponseMsgSizeSetting = env.RegisterIntegerSetting("ROX_GRPC_MAX_RESPONSE_SIZE", defaultMaxResponseMsgSize)
maxGrpcConcurrentStreamsSetting = env.RegisterIntegerSetting("ROX_GRPC_MAX_CONCURRENT_STREAMS", defaultMaxGrpcConcurrentStreams)
maxGrpcConcurrentStreamsSetting = env.RegisterIntegerSetting("ROX_GRPC_MAX_CONCURRENT_STREAMS", defaultMaxGrpcConcurrentStreams).WithMaximum(math.MaxUint32)
enableRequestTracing = env.RegisterBooleanSetting("ROX_GRPC_ENABLE_REQUEST_TRACING", false)
)

Expand All @@ -69,11 +70,12 @@ func maxResponseMsgSize() int {
}

func maxGrpcConcurrentStreams() uint32 {
if maxGrpcConcurrentStreamsSetting.IntegerSetting() <= 0 {
maxStreams := maxGrpcConcurrentStreamsSetting.IntegerSetting()
if maxStreams <= 0 {
return defaultMaxGrpcConcurrentStreams
}

return uint32(maxGrpcConcurrentStreamsSetting.IntegerSetting())
return uint32(maxStreams)
}

type server interface {
Expand Down
3 changes: 2 additions & 1 deletion sensor/common/virtualmachine/vmscraper/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ const (
)

func getVsockPort() uint32 {
return uint32(env.VirtualMachinesVsockPort.IntegerSetting())
port := env.VirtualMachinesVsockPort.IntegerSetting()
return uint32(port)
}

func clampPollInterval(interval time.Duration) time.Duration {
Expand Down
75 changes: 75 additions & 0 deletions tools/roxvet/analyzers/envintegercast/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package envintegercast

import (
"go/ast"
"go/types"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)

const envPkgPath = "github.com/stackrox/rox/pkg/env"

const doc = `check for direct narrowing casts from env integer settings`

var narrowedIntegerCasts = map[string]struct{}{
"int8": {},
"int16": {},
"int32": {},
"uint8": {},
"uint16": {},
"uint32": {},
}

// Analyzer is the analyzer.
var Analyzer = &analysis.Analyzer{
Name: "envintegercast",
Doc: doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
inspectResult := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
inspectResult.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(n ast.Node) {
call := n.(*ast.CallExpr)
cast, ok := call.Fun.(*ast.Ident)
if !ok {
return
}
if _, ok := narrowedIntegerCasts[cast.Name]; !ok || len(call.Args) != 1 {
return
}
if !isIntegerSettingCall(pass, call.Args[0]) {
return
}
pass.Reportf(call.Pos(), "avoid direct %s conversion of IntegerSetting(); validate or bound the setting before narrowing", cast.Name)
})
return nil, nil
}

func isIntegerSettingCall(pass *analysis.Pass, expr ast.Expr) bool {
call, ok := expr.(*ast.CallExpr)
if !ok {
return false
}
selector, ok := call.Fun.(*ast.SelectorExpr)
if !ok || selector.Sel.Name != "IntegerSetting" || len(call.Args) != 0 {
return false
}

selection := pass.TypesInfo.Selections[selector]
if selection == nil {
return false
}
return isEnvIntegerSettingMethod(selection.Obj())
}

func isEnvIntegerSettingMethod(obj types.Object) bool {
fn, ok := obj.(*types.Func)
if !ok || fn.Pkg() == nil || fn.Pkg().Path() != envPkgPath {
return false
}
return fn.Name() == "IntegerSetting"
}
12 changes: 12 additions & 0 deletions tools/roxvet/analyzers/envintegercast/analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package envintegercast

import (
"testing"

"golang.org/x/tools/go/analysis/analysistest"
)

func TestAnalyzer(t *testing.T) {
t.Parallel()
analysistest.Run(t, analysistest.TestData(), Analyzer, "a")
}
25 changes: 25 additions & 0 deletions tools/roxvet/analyzers/envintegercast/testdata/src/a/a.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package a

import "github.com/stackrox/rox/pkg/env"

type Setting struct{}

func (Setting) IntegerSetting() int { return 42 }
func (Setting) Setting() string { return "42" }

var localSetting Setting
var maxRows = env.RegisterIntegerSetting("ROX_TEST_MAX_ROWS", 100)

func directNarrowingCasts() {
_ = int32(maxRows.IntegerSetting()) // want `avoid direct int32 conversion of IntegerSetting\(\); validate or bound the setting before narrowing`
_ = uint32(maxRows.IntegerSetting()) // want `avoid direct uint32 conversion of IntegerSetting\(\); validate or bound the setting before narrowing`
}

func allowedCasts() {
_ = int(maxRows.IntegerSetting())
value := maxRows.IntegerSetting()
_ = int32(value)
_ = int32(0)
_ = int32(localSetting.IntegerSetting())
_ = int32(len(localSetting.Setting()))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package env

type IntegerSetting struct{}

func RegisterIntegerSetting(string, int) *IntegerSetting { return &IntegerSetting{} }

func (*IntegerSetting) IntegerSetting() int { return 42 }
2 changes: 2 additions & 0 deletions tools/roxvet/roxvet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"github.com/stackrox/rox/tools/roxvet/analyzers/donotcompareproto"
"github.com/stackrox/rox/tools/roxvet/analyzers/dontprintferr"
"github.com/stackrox/rox/tools/roxvet/analyzers/envintegercast"
"github.com/stackrox/rox/tools/roxvet/analyzers/filepathwalk"
"github.com/stackrox/rox/tools/roxvet/analyzers/godoccapitalizationmismatch"
"github.com/stackrox/rox/tools/roxvet/analyzers/gogoprotofunctions"
Expand All @@ -29,6 +30,7 @@ func main() {
unitchecker.Main(
donotcompareproto.Analyzer,
dontprintferr.Analyzer,
envintegercast.Analyzer,
filepathwalk.Analyzer,
godoccapitalizationmismatch.Analyzer,
gogoprotofunctions.Analyzer,
Expand Down