Skip to content

Commit 6d9e6f2

Browse files
committed
integer overflow protection
1 parent 5599482 commit 6d9e6f2

File tree

3 files changed

+163
-4
lines changed

3 files changed

+163
-4
lines changed

internal/applicationsnapshot/attestation.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package applicationsnapshot
1919
import (
2020
"bytes"
2121
"encoding/json"
22+
"math"
2223

2324
"github.com/in-toto/in-toto-golang/in_toto"
2425

@@ -53,7 +54,14 @@ func NewAttestationResult(att attestation.Attestation) AttestationResult {
5354
}
5455

5556
func (r *Report) renderAttestations() ([]byte, error) {
56-
byts := make([][]byte, 0, len(r.Components)*2)
57+
// Safe capacity calculation with overflow protection
58+
componentCount := len(r.Components)
59+
capacity := componentCount * 2
60+
if componentCount > math.MaxInt/2 {
61+
// If doubling would overflow, use a reasonable maximum
62+
capacity = math.MaxInt / 4
63+
}
64+
byts := make([][]byte, 0, capacity)
5765

5866
for _, c := range r.Components {
5967
for _, a := range c.Attestations {

internal/utils/safe_arithmetic.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright The Conforma Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// SPDX-License-Identifier: Apache-2.0
16+
17+
package utils
18+
19+
import (
20+
"fmt"
21+
"math"
22+
)
23+
24+
// SafeAdd safely adds two integers, returning an error if overflow would occur
25+
func SafeAdd(a, b int) (int, error) {
26+
if a > 0 && b > math.MaxInt-a {
27+
return 0, fmt.Errorf("integer overflow: %d + %d would exceed MaxInt", a, b)
28+
}
29+
if a < 0 && b < math.MinInt-a {
30+
return 0, fmt.Errorf("integer overflow: %d + %d would exceed MinInt", a, b)
31+
}
32+
return a + b, nil
33+
}
34+
35+
// SafeMultiply safely multiplies two integers, returning an error if overflow would occur
36+
func SafeMultiply(a, b int) (int, error) {
37+
if a == 0 || b == 0 {
38+
return 0, nil
39+
}
40+
41+
// Check for overflow
42+
if a > 0 && b > 0 {
43+
if a > math.MaxInt/b {
44+
return 0, fmt.Errorf("integer overflow: %d * %d would exceed MaxInt", a, b)
45+
}
46+
} else if a < 0 && b < 0 {
47+
if a < math.MaxInt/b {
48+
return 0, fmt.Errorf("integer overflow: %d * %d would exceed MaxInt", a, b)
49+
}
50+
} else if a > 0 && b < 0 {
51+
if b < math.MinInt/a {
52+
return 0, fmt.Errorf("integer overflow: %d * %d would exceed MinInt", a, b)
53+
}
54+
} else if a < 0 && b > 0 {
55+
if a < math.MinInt/b {
56+
return 0, fmt.Errorf("integer overflow: %d * %d would exceed MinInt", a, b)
57+
}
58+
}
59+
60+
return a * b, nil
61+
}
62+
63+
// SafeCapacity calculates a safe capacity for slice allocation
64+
// It prevents integer overflow and provides reasonable fallbacks
65+
func SafeCapacity(base int, multiplier int) int {
66+
capacity, err := SafeMultiply(base, multiplier)
67+
if err != nil {
68+
// Fallback to a reasonable maximum
69+
return math.MaxInt / 4
70+
}
71+
72+
// Additional safety check for extremely large values
73+
const maxReasonableCapacity = 10000000 // 10 million
74+
if capacity > maxReasonableCapacity {
75+
return maxReasonableCapacity
76+
}
77+
78+
return capacity
79+
}
80+
81+
// SafeSliceCapacity calculates safe capacity for slice allocation with multiple length additions
82+
func SafeSliceCapacity(lengths ...int) int {
83+
total := 0
84+
for _, length := range lengths {
85+
sum, err := SafeAdd(total, length)
86+
if err != nil {
87+
// Fallback to reasonable maximum
88+
return math.MaxInt / 4
89+
}
90+
total = sum
91+
}
92+
93+
// Additional safety check
94+
const maxReasonableCapacity = 10000000 // 10 million
95+
if total > maxReasonableCapacity {
96+
return maxReasonableCapacity
97+
}
98+
99+
return total
100+
}
101+
102+
// ValidateSliceLength validates that a slice length is within reasonable bounds
103+
func ValidateSliceLength(length int, maxAllowed int) error {
104+
if length < 0 {
105+
return fmt.Errorf("negative slice length: %d", length)
106+
}
107+
if length > maxAllowed {
108+
return fmt.Errorf("slice length %d exceeds maximum allowed %d", length, maxAllowed)
109+
}
110+
return nil
111+
}

internal/validate/vsa/validation.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/base64"
2323
"encoding/json"
2424
"fmt"
25+
"math"
2526
"strings"
2627
"sync"
2728

@@ -401,8 +402,28 @@ func validateAllComponentsFromPredicate(ctx context.Context, predicate *Predicat
401402

402403
// Pre-allocate slices with estimated capacity for better performance
403404
componentCount := len(predicate.Results.Components)
404-
allMissingRules := make([]MissingRule, 0, componentCount*2) // Estimate 2 missing rules per component
405-
allFailingRules := make([]FailingRule, 0, componentCount*2) // Estimate 2 failing rules per component
405+
406+
// Validate input bounds to prevent DoS attacks
407+
if componentCount < 0 {
408+
return nil, fmt.Errorf("negative component count: %d", componentCount)
409+
}
410+
411+
// Reasonable limit to prevent DoS attacks while allowing legitimate large predicates
412+
const maxComponents = 1000000 // 1 million components max
413+
if componentCount > maxComponents {
414+
return nil, fmt.Errorf("VSA predicate has too many components: %d (max: %d). This may indicate a malformed or malicious predicate.",
415+
componentCount, maxComponents)
416+
}
417+
418+
// Safe capacity calculation with overflow protection
419+
capacity := componentCount * 2
420+
if componentCount > math.MaxInt/2 {
421+
// If doubling would overflow, use a reasonable maximum
422+
capacity = math.MaxInt / 4
423+
}
424+
425+
allMissingRules := make([]MissingRule, 0, capacity)
426+
allFailingRules := make([]FailingRule, 0, capacity)
406427

407428
var totalPassingCount, totalRequired int
408429

@@ -525,7 +546,26 @@ func createValidationResult(missingRules []MissingRule, failingRules []FailingRu
525546
// It processes successes, violations, and warnings into a unified rule results map.
526547
func extractRuleResultsFromComponent(component applicationsnapshot.Component) map[string][]RuleResult {
527548
// Pre-allocate with estimated capacity for better performance
528-
totalRules := len(component.Successes) + len(component.Violations) + len(component.Warnings)
549+
// Safe addition to prevent integer overflow
550+
successes := len(component.Successes)
551+
violations := len(component.Violations)
552+
warnings := len(component.Warnings)
553+
554+
// Safe addition to prevent integer overflow
555+
var totalRules int
556+
// Check if any individual length is too large first
557+
if successes > math.MaxInt/3 || violations > math.MaxInt/3 || warnings > math.MaxInt/3 {
558+
// Individual slice too large, use conservative estimate
559+
totalRules = math.MaxInt / 4
560+
} else {
561+
// Safe to add - check for overflow
562+
totalRules = successes + violations + warnings
563+
if totalRules < 0 {
564+
// Overflow detected (result wrapped to negative)
565+
totalRules = math.MaxInt / 4
566+
}
567+
}
568+
529569
ruleResults := make(map[string][]RuleResult, totalRules/2) // Estimate 2 rules per ruleID
530570

531571
// Process all rule types using a helper function to reduce code duplication

0 commit comments

Comments
 (0)