Browse Source

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.
MHSanaei 20 hours ago
parent
commit
ab1c509329

+ 1 - 1
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)

+ 11 - 2
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
+}

+ 17 - 1
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)
+	}
+}