207 lines
5.4 KiB
Go
207 lines
5.4 KiB
Go
// Package integration provides integration tests for memory leak detection.
|
|
//
|
|
//go:build integration
|
|
// +build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/awnumar/memguard"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/security/securebytes"
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/security/securestring"
|
|
)
|
|
|
|
// TestSecureStringMemoryLeak verifies SecureString doesn't leak memory.
|
|
func TestSecureStringMemoryLeak(t *testing.T) {
|
|
// Force GC before measurement
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memBefore runtime.MemStats
|
|
runtime.ReadMemStats(&memBefore)
|
|
|
|
// Perform 10000 SecureString operations
|
|
for i := 0; i < 10000; i++ {
|
|
s, err := securestring.NewSecureString("test data that should be cleaned up properly")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SecureString: %v", err)
|
|
}
|
|
_ = s.String() // Access the string
|
|
s.Wipe()
|
|
}
|
|
|
|
// Force GC after operations
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memAfter runtime.MemStats
|
|
runtime.ReadMemStats(&memAfter)
|
|
|
|
// Check for memory leak - allow up to 5MB growth for test overhead
|
|
heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc)
|
|
if heapGrowth > 5*1024*1024 {
|
|
t.Errorf("Possible memory leak in SecureString: heap grew by %d bytes", heapGrowth)
|
|
}
|
|
}
|
|
|
|
// TestSecureBytesMemoryLeak verifies SecureBytes doesn't leak memory.
|
|
func TestSecureBytesMemoryLeak(t *testing.T) {
|
|
// Force GC before measurement
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memBefore runtime.MemStats
|
|
runtime.ReadMemStats(&memBefore)
|
|
|
|
// Perform 10000 SecureBytes operations
|
|
for i := 0; i < 10000; i++ {
|
|
data := make([]byte, 64)
|
|
for j := range data {
|
|
data[j] = byte(i % 256)
|
|
}
|
|
|
|
sb, err := securebytes.NewSecureBytes(data)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SecureBytes: %v", err)
|
|
}
|
|
_ = sb.Bytes() // Access the bytes
|
|
sb.Wipe()
|
|
}
|
|
|
|
// Force GC after operations
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memAfter runtime.MemStats
|
|
runtime.ReadMemStats(&memAfter)
|
|
|
|
// Check for memory leak
|
|
heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc)
|
|
if heapGrowth > 5*1024*1024 {
|
|
t.Errorf("Possible memory leak in SecureBytes: heap grew by %d bytes", heapGrowth)
|
|
}
|
|
}
|
|
|
|
// TestMemguardWipeBytesEffectiveness verifies memguard.WipeBytes actually zeros memory.
|
|
func TestMemguardWipeBytesEffectiveness(t *testing.T) {
|
|
// Create sensitive data
|
|
sensitiveData := []byte("super-secret-password-12345")
|
|
originalLen := len(sensitiveData)
|
|
|
|
// Wipe the data
|
|
memguard.WipeBytes(sensitiveData)
|
|
|
|
// Verify all bytes are zero
|
|
for i, b := range sensitiveData {
|
|
if b != 0 {
|
|
t.Errorf("Byte at position %d not wiped: got %d, expected 0", i, b)
|
|
}
|
|
}
|
|
|
|
// Verify length unchanged
|
|
if len(sensitiveData) != originalLen {
|
|
t.Errorf("Length changed after wipe: got %d, expected %d", len(sensitiveData), originalLen)
|
|
}
|
|
}
|
|
|
|
// TestSecureStringWipeEffectiveness verifies SecureString.Wipe() actually cleans up.
|
|
func TestSecureStringWipeEffectiveness(t *testing.T) {
|
|
secret := "my-super-secret-token"
|
|
ss, err := securestring.NewSecureString(secret)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SecureString: %v", err)
|
|
}
|
|
|
|
// Verify we can access the string before wiping
|
|
if ss.String() != secret {
|
|
t.Error("SecureString didn't store the original value correctly")
|
|
}
|
|
|
|
// Wipe the secure string
|
|
ss.Wipe()
|
|
|
|
// After wiping, String() should return empty
|
|
if ss.String() != "" {
|
|
t.Error("SecureString.String() should return empty after Wipe()")
|
|
}
|
|
}
|
|
|
|
// TestLockedBufferMemoryLeak verifies LockedBuffer doesn't leak memory.
|
|
func TestLockedBufferMemoryLeak(t *testing.T) {
|
|
// Force GC before measurement
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memBefore runtime.MemStats
|
|
runtime.ReadMemStats(&memBefore)
|
|
|
|
// Perform 10000 LockedBuffer operations
|
|
for i := 0; i < 10000; i++ {
|
|
buf := memguard.NewBuffer(64)
|
|
copy(buf.Bytes(), []byte("test-data-for-locked-buffer"))
|
|
buf.Destroy()
|
|
}
|
|
|
|
// Force GC after operations
|
|
runtime.GC()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
var memAfter runtime.MemStats
|
|
runtime.ReadMemStats(&memAfter)
|
|
|
|
// Check for memory leak
|
|
heapGrowth := int64(memAfter.HeapAlloc) - int64(memBefore.HeapAlloc)
|
|
if heapGrowth > 5*1024*1024 {
|
|
t.Errorf("Possible memory leak in LockedBuffer: heap grew by %d bytes", heapGrowth)
|
|
}
|
|
}
|
|
|
|
// TestConcurrentSecureStringOperations tests thread safety.
|
|
func TestConcurrentSecureStringOperations(t *testing.T) {
|
|
done := make(chan bool, 10)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
go func(id int) {
|
|
defer func() { done <- true }()
|
|
|
|
for j := 0; j < 1000; j++ {
|
|
ss, err := securestring.NewSecureString("concurrent-test-data")
|
|
if err != nil {
|
|
t.Errorf("Goroutine %d: Failed to create SecureString: %v", id, err)
|
|
return
|
|
}
|
|
_ = ss.String()
|
|
ss.Wipe()
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
// TestRepeatedWipeCalls verifies multiple Wipe() calls don't panic.
|
|
func TestRepeatedWipeCalls(t *testing.T) {
|
|
ss, err := securestring.NewSecureString("test-data")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SecureString: %v", err)
|
|
}
|
|
|
|
// Multiple wipe calls should be safe
|
|
for i := 0; i < 10; i++ {
|
|
err := ss.Wipe()
|
|
if err != nil {
|
|
t.Errorf("Wipe() call %d returned error: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run integration tests with:
|
|
// go test -tags=integration -v ./test/integration/
|