Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions internal/memory/memory.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
// Package memory Is a slight copy of the memory storage, but far from the storage interface it can not only work with bytes
// but directly store any kind of data without having to encode it each time, which gives a huge speed advantage
// Package memory provides a high-performance in-memory storage that can store
// any type without encoding overhead. Unlike the standard storage interface,
// this storage works directly with Go types for maximum speed.
//
// # Safety Considerations
//
// This storage automatically performs defensive copying for:
// - String keys: Copied to prevent corruption from pooled buffers
// - []byte values: Copied on both Set and Get to prevent external mutation
//
// For other types (structs, ints, etc.), Go's value semantics provide natural
// protection. However, if storing pointers or slices of non-byte types,
// callers are responsible for not mutating the underlying data.
//
// This storage is primarily used internally by middleware for performance-
// critical operations where the stored data types are known and controlled.
package memory

import (
Expand Down Expand Up @@ -33,26 +47,48 @@ func New() *Storage {

// Get retrieves the value stored under key, returning nil when the entry does
// not exist or has expired.
//
// For []byte values, this returns a defensive copy to prevent callers from
// mutating the stored data. Other types are returned as-is.
func (s *Storage) Get(key string) any {
s.RLock()
v, ok := s.data[key]
s.RUnlock()
if !ok || v.e != 0 && v.e <= utils.Timestamp() {
return nil
}

// Defensive copy for byte slices to prevent external mutation
if b, ok := v.v.([]byte); ok {
return utils.CopyBytes(b)
}

return v.v
}

// Set stores val under key and applies the optional ttl before expiring the
// entry. A non-positive ttl keeps the item forever.
//
// String keys are defensively copied to prevent corruption from pooled buffers.
// []byte values are also copied to prevent external mutation of stored data.
// Other types are stored as-is (structs are copied by value automatically).
func (s *Storage) Set(key string, val any, ttl time.Duration) {
var exp uint32
if ttl > 0 {
exp = uint32(ttl.Seconds()) + utils.Timestamp()
}

// Defensive copies to prevent unsafe reuse from sync.Pool
keyCopy := utils.CopyString(key)
Comment thread
arturmelanchyk marked this conversation as resolved.

// Copy byte slices to prevent external mutation
if b, ok := val.([]byte); ok {
val = utils.CopyBytes(b)
}

i := item{e: exp, v: val}
s.Lock()
s.data[key] = i
s.data[keyCopy] = i
s.Unlock()
}

Expand Down
1 change: 1 addition & 0 deletions internal/storage/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (s *Storage) Get(key string) ([]byte, error) {
s.mux.RLock()
v, ok := s.db[key]
s.mux.RUnlock()

if !ok || v.expiry != 0 && v.expiry <= utils.Timestamp() {
return nil, nil
}
Expand Down
6 changes: 2 additions & 4 deletions middleware/csrf/storage_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/internal/memory"
"github.com/gofiber/utils/v2"
)

// msgp -file="storage_manager.go" -o="storage_manager_msgp.go" -tests=true -unexported
Expand Down Expand Up @@ -41,7 +40,7 @@ func newStorageManager(storage fiber.Storage, redactKeys bool) *storageManager {
// Use provided storage if provided
storageManager.storage = storage
} else {
// Fallback too memory storage
// Fallback to memory storage
storageManager.memory = memory.New()
}
return storageManager
Expand Down Expand Up @@ -77,8 +76,7 @@ func (m *storageManager) setRaw(ctx context.Context, key string, raw []byte, exp
return nil
}

// The key and value are crucial in csrf and can be references to data that might be reused (e.g., from a pool). To prevent unsafe value retention, copies of both the key and raw value are made here.
m.memory.Set(utils.CopyString(key), utils.CopyBytes(raw), exp)
m.memory.Set(key, raw, exp)
return nil
}

Expand Down
3 changes: 1 addition & 2 deletions middleware/idempotency/idempotency.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ func New(config ...Config) fiber.Handler {
// Construct response
res := &response{
StatusCode: c.Response().StatusCode(),

Body: utils.CopyBytes(c.Response().Body()),
Body: c.Response().Body(),
}
{
headers := make(map[string][]string)
Expand Down
Loading