Fix: Prevent memory corruption from pooled buffers in memory storage

Problem:
The memory storage uses string keys and byte slice values directly in
a Go map without copying them. When these strings/slices are backed by
pooled buffers (from sync.Pool used by Fiber for performance), the map
keys and values can become corrupted when those buffers are reused.

Root Cause:
1. Fiber v3 uses sync.Pool extensively for byte buffer reuse
2. Strings created from pooled buffers point to the underlying pooled memory
3. When used as Go map keys without copying, these strings share the pooled buffer
4. When the buffer is returned to the pool and reused, the map key gets corrupted
5. This causes intermittent failures where sessions/CSRF tokens cannot be found

Solution:
Copy both the key (string) and value ([]byte) before storing in the map.
Since this package doesn't have access to gofiber/utils, we use manual copying:
- Key: string([]byte(key)) - creates a new string with a new backing array
- Value: make new slice and copy bytes

Testing:
- Before fix: ~8% pass rate with ginkgo --repeat=100
- After fix: 100% pass rate with ginkgo --repeat=200
- No corrupted keys found in storage after fix

Impact:
- Performance: Minimal - one string copy and one byte slice copy per Set
- Safety: Prevents entire class of memory corruption bugs
- Consistency: Aligns with the fix applied to gofiber/fiber internal storage
This commit is contained in:
Jason McNeil
2025-10-31 05:48:39 -03:00
parent a0d6e6df5a
commit a3e93a4171

View File

@@ -64,13 +64,19 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
}
var expire uint32
// Copy both key and value to avoid unsafe reuse from sync.Pool
// When Fiber uses pooled buffers, the underlying memory can be reused
keyCopy := string([]byte(key))
valCopy := make([]byte, len(val))
copy(valCopy, val)
if exp != 0 {
expire = uint32(exp.Seconds()) + atomic.LoadUint32(&internal.Timestamp)
}
e := entry{val, expire}
e := entry{valCopy, expire}
s.mux.Lock()
s.db[key] = e
s.db[keyCopy] = e
s.mux.Unlock()
return nil
}