Fix cache serialisation (#117)

* Fix cache serialisation

* fix(cache): add integer overflow protection for serialization

- [x] Add maxCacheEntrySize constant (64 MiB) to prevent memory overflow
- [x] Validate byte slice size before adding marker byte
- [x] Validate JSON-serialized data size before marker addition
- [x] Add comprehensive overflow protection test cases
This commit is contained in:
2026-01-08 22:06:19 +00:00
committed by GitHub
parent 7816e05c98
commit 775de2ada1
2 changed files with 592 additions and 4 deletions
+75 -4
View File
@@ -21,6 +21,10 @@ const (
CacheTypeJWK CacheType = "jwk"
CacheTypeSession CacheType = "session"
CacheTypeGeneral CacheType = "general"
// maxCacheEntrySize defines the maximum size for a single cache entry (64 MiB)
// This prevents integer overflow when allocating memory for serialization
maxCacheEntrySize = 64 * 1024 * 1024
)
// UniversalCacheConfig provides configuration for the universal cache
@@ -768,14 +772,81 @@ func (c *UniversalCache) Strategy() CacheStrategy {
// serialize converts a value to bytes for backend storage
func (c *UniversalCache) serialize(value interface{}) ([]byte, error) {
// Use JSON for serialization - simple and universal
return json.Marshal(value)
// If value is already a byte slice (e.g., pre-marshaled JSON from metadata_cache),
// store it directly with a marker to prevent double-encoding.
// This fixes the issue where []byte was being JSON-marshaled, causing Base64 encoding.
if bytes, ok := value.([]byte); ok {
// Validate size to prevent integer overflow
if len(bytes) > maxCacheEntrySize {
return nil, fmt.Errorf("cache entry size %d exceeds maximum allowed size %d", len(bytes), maxCacheEntrySize)
}
// Check for potential overflow when adding marker byte
if len(bytes) == maxCacheEntrySize {
return nil, fmt.Errorf("cache entry size would overflow when adding marker byte")
}
// Prepend marker byte 0x00 to indicate raw bytes (not JSON-encoded)
result := make([]byte, len(bytes)+1)
result[0] = 0x00
copy(result[1:], bytes)
return result, nil
}
// For all other types (maps, strings, etc.), use JSON encoding
// Prepend marker byte 0x01 to indicate JSON-encoded data
jsonData, err := json.Marshal(value)
if err != nil {
return nil, err
}
// Validate size to prevent integer overflow
if len(jsonData) > maxCacheEntrySize {
return nil, fmt.Errorf("serialized cache entry size %d exceeds maximum allowed size %d", len(jsonData), maxCacheEntrySize)
}
// Check for potential overflow when adding marker byte
if len(jsonData) == maxCacheEntrySize {
return nil, fmt.Errorf("serialized cache entry size would overflow when adding marker byte")
}
result := make([]byte, len(jsonData)+1)
result[0] = 0x01
copy(result[1:], jsonData)
return result, nil
}
// deserialize converts bytes from backend storage to a value
func (c *UniversalCache) deserialize(data []byte, value interface{}) error {
// Use JSON for deserialization
return json.Unmarshal(data, value)
if len(data) == 0 {
return fmt.Errorf("cannot deserialize empty data")
}
// Check for type marker (added by serialize)
if data[0] == 0x00 {
// Raw bytes - strip marker and return as-is
rawBytes := data[1:]
if ptr, ok := value.(*interface{}); ok {
*ptr = rawBytes
return nil
}
return fmt.Errorf("cannot deserialize raw bytes into %T", value)
}
if data[0] == 0x01 {
// JSON-encoded - strip marker and unmarshal
return json.Unmarshal(data[1:], value)
}
// Legacy data without marker (for backward compatibility)
// Try to unmarshal as JSON
if err := json.Unmarshal(data, value); err != nil {
// If unmarshal fails, treat as raw bytes
if ptr, ok := value.(*interface{}); ok {
*ptr = data
return nil
}
return err
}
return nil
}
// prefixKey adds a cache type prefix to the key for backend storage