From ab1c5093291fd3cd0a0df314f7c673fe21a2431d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 11 Oct 2026 22:10:46 +0200 Subject: [PATCH] fix(service): keep chunk capacity math from overflowing chunkStrings/chunkInts sized their result with (len+size-1)/size, so a size near MaxInt wrapped negative and make panicked with "cap out of range"; count the chunks with div+remainder instead. NormalizeUDPHop's len-hops+1 is bounded by len(masks) since hops >= 1, so cap at that. Resolves CodeQL go/allocation-size-overflow alerts #119 and #120. --- internal/util/maskcompat/udphop.go | 2 +- internal/web/service/inbound_util.go | 13 +++++++++++-- internal/web/service/inbound_util_test.go | 18 +++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/internal/util/maskcompat/udphop.go b/internal/util/maskcompat/udphop.go index 92d7ba2f7..a8c4d0c9a 100644 --- a/internal/util/maskcompat/udphop.go +++ b/internal/util/maskcompat/udphop.go @@ -51,7 +51,7 @@ func NormalizeUDPHop(finalmask any) bool { if last < 0 || (hops == 1 && last == len(masks)-1) { return false } - kept := make([]any, 0, len(masks)-hops+1) + kept := make([]any, 0, len(masks)) // hops >= 1, so the re-appended hop still fits for _, mask := range masks { if !isUDPHopMask(mask) { kept = append(kept, mask) diff --git a/internal/web/service/inbound_util.go b/internal/web/service/inbound_util.go index d3ac8098a..2917612d0 100644 --- a/internal/web/service/inbound_util.go +++ b/internal/web/service/inbound_util.go @@ -72,7 +72,7 @@ func chunkStrings(s []string, size int) [][]string { if size <= 0 || len(s) == 0 { return nil } - out := make([][]string, 0, (len(s)+size-1)/size) + out := make([][]string, 0, chunkCount(len(s), size)) for i := 0; i < len(s); i += size { end := min(i+size, len(s)) out = append(out, s[i:end]) @@ -86,10 +86,19 @@ func chunkInts(s []int, size int) [][]int { if size <= 0 || len(s) == 0 { return nil } - out := make([][]int, 0, (len(s)+size-1)/size) + out := make([][]int, 0, chunkCount(len(s), size)) for i := 0; i < len(s); i += size { end := min(i+size, len(s)) out = append(out, s[i:end]) } return out } + +// chunkCount is ceil(n/size) without the n+size-1 sum, which overflows for a size near MaxInt. +func chunkCount(n, size int) int { + count := n / size + if n%size != 0 { + count++ + } + return count +} diff --git a/internal/web/service/inbound_util_test.go b/internal/web/service/inbound_util_test.go index c4a5f46ba..5775087a2 100644 --- a/internal/web/service/inbound_util_test.go +++ b/internal/web/service/inbound_util_test.go @@ -1,6 +1,10 @@ package service -import "testing" +import ( + "math" + "slices" + "testing" +) func TestNormalizeSubSortIndex(t *testing.T) { cases := []struct { @@ -18,3 +22,15 @@ func TestNormalizeSubSortIndex(t *testing.T) { } } } + +// A size near MaxInt used to overflow the capacity math and panic in make (CodeQL #119). +func TestChunkHugeSizeKeepsOneBatch(t *testing.T) { + strs := []string{"a", "b", "c"} + if got := chunkStrings(strs, math.MaxInt); len(got) != 1 || !slices.Equal(got[0], strs) { + t.Fatalf("chunkStrings(MaxInt) = %v, want [%v]", got, strs) + } + ints := []int{1, 2, 3} + if got := chunkInts(ints, math.MaxInt); len(got) != 1 || !slices.Equal(got[0], ints) { + t.Fatalf("chunkInts(MaxInt) = %v, want [%v]", got, ints) + } +}