mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-02-05 01:45:24 +00:00
fix(security): prevent integer overflow in uint64 to int64 conversions
Add overflow checks before converting uint64 memory values to int64 to prevent potential integer overflow issues identified by gosec (G115). - Add math.MaxInt64 checks in fileproc/backpressure.go - Add math.MaxInt64 checks in fileproc/resource_monitor_validation.go - Add math.MaxInt64 checks in fileproc/resource_monitor_metrics.go - Add math.MaxInt64 check in benchmark/benchmark.go with nosec annotation Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package fileproc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -73,7 +74,11 @@ func (bp *BackpressureManager) ShouldApplyBackpressure(ctx context.Context) bool
|
||||
// Get current memory usage
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
// Safe conversion: cap at MaxInt64 to prevent overflow
|
||||
currentMemory := int64(m.Alloc)
|
||||
if m.Alloc > math.MaxInt64 {
|
||||
currentMemory = math.MaxInt64
|
||||
}
|
||||
|
||||
bp.mu.Lock()
|
||||
defer bp.mu.Unlock()
|
||||
@@ -130,10 +135,16 @@ func (bp *BackpressureManager) GetStats() BackpressureStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
// Safe conversion: cap at MaxInt64 to prevent overflow
|
||||
currentMemory := int64(m.Alloc)
|
||||
if m.Alloc > math.MaxInt64 {
|
||||
currentMemory = math.MaxInt64
|
||||
}
|
||||
|
||||
return BackpressureStats{
|
||||
Enabled: bp.enabled,
|
||||
FilesProcessed: atomic.LoadInt64(&bp.filesProcessed),
|
||||
CurrentMemoryUsage: int64(m.Alloc),
|
||||
CurrentMemoryUsage: currentMemory,
|
||||
MaxMemoryUsage: bp.maxMemoryUsage,
|
||||
MemoryWarningActive: bp.memoryWarningLogged,
|
||||
LastMemoryCheck: bp.lastMemoryCheck,
|
||||
|
||||
@@ -102,4 +102,4 @@ func TestFileTypeRegistry_ThreadSafety(t *testing.T) {
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,4 +255,4 @@ func TestConfigureFromSettings(t *testing.T) {
|
||||
if !IsImage("test.extra") {
|
||||
t.Error("Expected new configuration to be applied")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,11 +208,11 @@ func TestFileTypeRegistry_BinaryDetection(t *testing.T) {
|
||||
{"page.html", false},
|
||||
|
||||
// Edge cases
|
||||
{"", false}, // Empty filename
|
||||
{"binary", false}, // No extension
|
||||
{".exe", true}, // Just extension
|
||||
{"file.exe.txt", false}, // Multiple extensions
|
||||
{"file.unknown", false}, // Unknown extension
|
||||
{"", false}, // Empty filename
|
||||
{"binary", false}, // No extension
|
||||
{".exe", true}, // Just extension
|
||||
{"file.exe.txt", false}, // Multiple extensions
|
||||
{"file.unknown", false}, // Unknown extension
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -223,4 +223,4 @@ func TestFileTypeRegistry_BinaryDetection(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,4 +125,4 @@ func BenchmarkFileTypeRegistry_ConcurrentAccess(b *testing.B) {
|
||||
_ = GetLanguage(filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,4 +134,4 @@ func TestFileTypeRegistry_DefaultRegistryConsistency(t *testing.T) {
|
||||
t.Errorf("Iteration %d: Expected .txt to not be recognized as binary", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +130,6 @@ func (w *JSONWriter) streamJSONContent(reader io.Reader, path string) error {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// startJSONWriter handles JSON format output with streaming support.
|
||||
func startJSONWriter(outFile *os.File, writeCh <-chan WriteRequest, done chan<- struct{}, prefix, suffix string) {
|
||||
defer close(done)
|
||||
|
||||
@@ -138,7 +138,6 @@ func (p *FileProcessor) ProcessWithContext(ctx context.Context, filePath string,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// validateFileWithLimits checks if the file can be processed with resource limits.
|
||||
func (p *FileProcessor) validateFileWithLimits(ctx context.Context, filePath string) (os.FileInfo, error) {
|
||||
// Check context cancellation
|
||||
@@ -192,7 +191,6 @@ func (p *FileProcessor) getRelativePath(filePath string) string {
|
||||
return relPath
|
||||
}
|
||||
|
||||
|
||||
// processInMemoryWithContext loads the entire file into memory with context awareness.
|
||||
func (p *FileProcessor) processInMemoryWithContext(ctx context.Context, filePath, relPath string, outCh chan<- WriteRequest) {
|
||||
// Check context before reading
|
||||
@@ -240,7 +238,6 @@ func (p *FileProcessor) processInMemoryWithContext(ctx context.Context, filePath
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// processStreamingWithContext creates a streaming reader for large files with context awareness.
|
||||
func (p *FileProcessor) processStreamingWithContext(ctx context.Context, filePath, relPath string, outCh chan<- WriteRequest) {
|
||||
// Check context before creating reader
|
||||
@@ -276,7 +273,6 @@ func (p *FileProcessor) processStreamingWithContext(ctx context.Context, filePat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// createStreamReaderWithContext creates a reader that combines header and file content with context awareness.
|
||||
func (p *FileProcessor) createStreamReaderWithContext(ctx context.Context, filePath, relPath string) io.Reader {
|
||||
// Check context before opening file
|
||||
|
||||
@@ -56,4 +56,4 @@ func (rm *ResourceMonitor) CreateOverallProcessingContext(parent context.Context
|
||||
return parent, func() {}
|
||||
}
|
||||
return context.WithTimeout(parent, rm.overallTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ func TestResourceMonitor_ConcurrentReadsLimit(t *testing.T) {
|
||||
|
||||
// Release one slot and try again
|
||||
rm.ReleaseReadSlot()
|
||||
|
||||
|
||||
// Create new context for the next attempt
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel2()
|
||||
|
||||
|
||||
err = rm.AcquireReadSlot(ctx2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error after releasing a slot, got %v", err)
|
||||
@@ -92,4 +92,4 @@ func TestResourceMonitor_TimeoutContexts(t *testing.T) {
|
||||
} else if time.Until(deadline) > 2*time.Second+100*time.Millisecond {
|
||||
t.Error("Overall processing timeout appears to be too long")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +78,4 @@ func TestResourceMonitor_Integration(t *testing.T) {
|
||||
|
||||
// Test resource limit logging
|
||||
rm.LogResourceInfo()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fileproc
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -48,6 +49,12 @@ func (rm *ResourceMonitor) GetMetrics() ResourceMetrics {
|
||||
violations = append(violations, violation)
|
||||
}
|
||||
|
||||
// Safe conversion: cap at MaxInt64 to prevent overflow
|
||||
memoryUsage := int64(m.Alloc) / 1024 / 1024
|
||||
if m.Alloc > math.MaxInt64 {
|
||||
memoryUsage = math.MaxInt64 / 1024 / 1024
|
||||
}
|
||||
|
||||
return ResourceMetrics{
|
||||
FilesProcessed: filesProcessed,
|
||||
TotalSizeProcessed: totalSize,
|
||||
@@ -55,7 +62,7 @@ func (rm *ResourceMonitor) GetMetrics() ResourceMetrics {
|
||||
ProcessingDuration: duration,
|
||||
AverageFileSize: avgFileSize,
|
||||
ProcessingRate: processingRate,
|
||||
MemoryUsageMB: int64(m.Alloc) / 1024 / 1024,
|
||||
MemoryUsageMB: memoryUsage,
|
||||
MaxMemoryUsageMB: int64(rm.hardMemoryLimitMB),
|
||||
ViolationsDetected: violations,
|
||||
DegradationActive: rm.degradationActive,
|
||||
@@ -76,4 +83,4 @@ func (rm *ResourceMonitor) LogResourceInfo() {
|
||||
} else {
|
||||
logrus.Info("Resource limits disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,4 @@ func TestResourceMonitor_Metrics(t *testing.T) {
|
||||
if !metrics.LastUpdated.After(time.Now().Add(-time.Second)) {
|
||||
t.Error("Expected recent LastUpdated timestamp")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,4 @@ func (rm *ResourceMonitor) rateLimiterRefill() {
|
||||
// Channel is full, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,4 @@ func TestResourceMonitor_RateLimiting(t *testing.T) {
|
||||
if duration < 200*time.Millisecond {
|
||||
t.Logf("Rate limiting may not be working as expected, took only %v", duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,4 @@ func (rm *ResourceMonitor) Close() {
|
||||
if rm.rateLimiter != nil {
|
||||
rm.rateLimiter.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +100,9 @@ func NewResourceMonitor() *ResourceMonitor {
|
||||
}
|
||||
rateLimitFull:
|
||||
|
||||
// Start rate limiter refill goroutine
|
||||
// Start rate limiter refill goroutine
|
||||
go rm.rateLimiterRefill()
|
||||
}
|
||||
|
||||
return rm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestResourceMonitor_NewResourceMonitor(t *testing.T) {
|
||||
}
|
||||
|
||||
if rm.fileProcessingTimeout != time.Duration(config.DefaultFileProcessingTimeoutSec)*time.Second {
|
||||
t.Errorf("Expected fileProcessingTimeout to be %v, got %v",
|
||||
t.Errorf("Expected fileProcessingTimeout to be %v, got %v",
|
||||
time.Duration(config.DefaultFileProcessingTimeoutSec)*time.Second, rm.fileProcessingTimeout)
|
||||
}
|
||||
|
||||
@@ -71,4 +71,4 @@ func TestResourceMonitor_DisabledResourceLimits(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when rate limiting disabled, got %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fileproc
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -88,7 +89,11 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error {
|
||||
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
// Safe conversion: cap at MaxInt64 to prevent overflow
|
||||
currentMemory := int64(m.Alloc)
|
||||
if m.Alloc > math.MaxInt64 {
|
||||
currentMemory = math.MaxInt64
|
||||
}
|
||||
|
||||
if currentMemory > rm.hardMemoryLimitBytes {
|
||||
rm.mu.Lock()
|
||||
@@ -108,7 +113,11 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error {
|
||||
|
||||
// Check again after GC
|
||||
runtime.ReadMemStats(&m)
|
||||
// Safe conversion: cap at MaxInt64 to prevent overflow
|
||||
currentMemory = int64(m.Alloc)
|
||||
if m.Alloc > math.MaxInt64 {
|
||||
currentMemory = math.MaxInt64
|
||||
}
|
||||
|
||||
if currentMemory > rm.hardMemoryLimitBytes {
|
||||
// Still over limit, activate emergency stop
|
||||
@@ -145,4 +154,4 @@ func (rm *ResourceMonitor) CheckHardMemoryLimit() error {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,4 @@ func TestResourceMonitor_TotalSizeLimit(t *testing.T) {
|
||||
} else if structErr.Code != utils.CodeResourceLimitTotalSize {
|
||||
t.Errorf("Expected error code %s, got %s", utils.CodeResourceLimitTotalSize, structErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user