Skip to content

Fix: Prevent memory corruption in internal memory storage from pooled buffers#3828

Merged
ReneWerner87 merged 3 commits intogofiber:mainfrom
sixcolors:fix/internal-memory-storage-pool-corruption
Oct 31, 2025
Merged

Fix: Prevent memory corruption in internal memory storage from pooled buffers#3828
ReneWerner87 merged 3 commits intogofiber:mainfrom
sixcolors:fix/internal-memory-storage-pool-corruption

Conversation

@sixcolors
Copy link
Member

@sixcolors sixcolors commented Oct 31, 2025

Problem

Found while investigating an error reported in the example: https://github.com/simonnix/fiber-csrf-v3-test/blob/main/csrf-v3.sh

The internal memory storage in internal/storage/memory/memory.go 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 in storage

Observed Behavior

  • Test failure rate: ~92% with ginkgo --repeat=100
  • Symptoms: Session IDs being replaced by corrupted CSRF token IDs
  • Example corruption: CSRF-...-3599f46c33edaa9 (expected: CSRF-...-3599f46c33ed, actual has extra aa9 from session ID)
  • Only occurs in repeated tests within same process (ginkgo --repeat), not in isolated tests

Solution

Copy both the key (string) and value ([]byte) before storing in the map using utils.CopyString() and utils.CopyBytes().

// Copy both key and value to avoid unsafe reuse from sync.Pool
keyCopy := utils.CopyString(key)
valCopy := utils.CopyBytes(val)

e := entry{data: valCopy, expiry: expire}
s.mux.Lock()
s.db[keyCopy] = e
s.mux.Unlock()

Testing

  • Before fix: 4/50 passes with ginkgo --repeat=50 (~8% pass rate)
  • After fix: 200/200 passes with ginkgo --repeat=200 (100% pass rate)
  • No corrupted keys found in storage logs after fix

Impact

  • Performance: Minimal - one string copy and one byte slice copy per Set operation
  • Safety: Prevents entire class of memory corruption bugs
  • Consistency: Aligns with defensive programming at API boundaries
  • Middleware: The CSRF middleware already documents this issue and implements the same workaround for its internal memory storage (middleware/csrf/storage_manager.go:82)

Files Changed

  • internal/storage/memory/memory.go - Added defensive copying in Set() method

… buffers

The internal 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.

This fix adds defensive copying of both keys and values before storing them
in the map using utils.CopyString() and utils.CopyBytes().

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

Impact:
- Before fix: ~8% test pass rate with repeated tests (ginkgo --repeat=50)
- After fix: 100% test pass rate (200/200 passes with ginkgo --repeat=200)
- Minimal performance overhead (one string copy + one slice copy per Set)

The CSRF middleware already documents this issue and implements the same
workaround for its internal memory storage (middleware/csrf/storage_manager.go:82).
Copilot AI review requested due to automatic review settings October 31, 2025 08:44
@sixcolors sixcolors requested a review from a team as a code owner October 31, 2025 08:44
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Get now returns a defensive copy of stored values; Set copies both key and value before inserting into the in-memory map to avoid unsafe reuse or external mutation (changes limited to copy behavior and comments in internal/storage/memory/memory.go).

Changes

Cohort / File(s) Change Summary
Memory storage — copy semantics
internal/storage/memory/memory.go
Set now copies the key and value (stores using keyCopy and valCopy with expiry) before map assignment; Get returns a copied slice via utils.CopyBytes. Added comments explaining defensive copies to avoid reuse/mutation issues.

Sequence Diagram(s)

sequenceDiagram
    participant C as Caller
    participant S as MemStorage

    Note over C,S #d5f5e3: Set flow (new)
    C->>S: Set(key, value, ttl)
    S-->>S: keyCopy = copy(key)\nvalCopy = copy(value)
    S-->>S: store map[keyCopy] = {data: valCopy, expiry}
    S->>C: OK

    Note over C,S #fce8d6: Get flow (new)
    C->>S: Get(key)
    S-->>S: entry = map[key]
    S-->>S: return copy(entry.data)
    S->>C: valueCopy
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Review internal/storage/memory/memory.go for correct and consistent copying of keys/values.
  • Verify tests or callers relying on identity semantics (if any) are updated or unaffected.

Possibly related PRs

Suggested labels

☢️ Bug, v3

Suggested reviewers

  • gaby
  • ReneWerner87
  • efectn

Poem

🐰 I copied keys and copied bytes,
Hopped past pools and midnight frights;
Now stored safe in cozy nests,
No more mutated, troubled guests! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The pull request description provided by the author is comprehensive and well-written from a technical perspective, covering the problem statement, root cause analysis, solution with code examples, testing results, and impact assessment. However, it deviates significantly from the required template structure. The description is missing several critical template sections including the "Fixes # (issue)" reference, the "Type of change" checkboxes (no selection made from the provided options), the structured "Changes introduced" section with required checklist items, and the mandatory pre-submission checklist items. While the technical content is strong and not off-topic, the lack of template adherence and missing required structural elements prevent this from being considered a complete PR description per the repository requirements. The author should revise the PR description to follow the required template structure. Specifically, add the missing sections: include a "Fixes # (issue)" reference at the top, select one or more options from the "Type of change" section (this appears to be "Code consistency" and/or "Performance improvement"), populate the "Changes introduced" section with applicable checklist items (particularly Documentation Update and Changelog/What's New), complete the pre-submission checklist by checking relevant items, and add a note about commit formatting with emojis. The existing technical content can be preserved and incorporated into the appropriate template sections.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Fix: Prevent memory corruption in internal memory storage from pooled buffers" directly addresses the main change in the changeset. The title is specific and clearly communicates that the fix addresses memory corruption issues caused by pooled buffers in the internal memory storage component. It is concise, avoids vague terminology, and a teammate reviewing the history would immediately understand the primary purpose of this change—preventing memory corruption through defensive copying.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @sixcolors, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical memory corruption bug within the internal memory storage mechanism. Previously, the system was vulnerable to data corruption when storing string keys and byte slice values directly from pooled buffers, as their underlying memory could be unexpectedly altered upon buffer reuse. The solution involves implementing defensive copying for both keys and values before they are stored, effectively isolating the map's data from external buffer reuse. This change ensures data integrity and significantly enhances the stability and reliability of the storage component with minimal performance overhead.

Highlights

  • Memory Corruption Fix: Addressed a critical memory corruption issue in the internal memory storage (internal/storage/memory/memory.go) where map keys and values could become corrupted due to the reuse of pooled buffers.
  • Root Cause Identified: Pinpointed the root cause to Go map keys and byte slice values being directly backed by sync.Pool buffers, leading to corruption when these buffers were returned to the pool and reused.
  • Defensive Copying Implemented: Introduced defensive copying of both string keys and byte slice values using utils.CopyString() and utils.CopyBytes() respectively, before storage, ensuring data integrity.
  • Improved Stability: Significantly improved test stability, moving from an ~8% pass rate to a 100% pass rate in repeated tests, eliminating observed corruption and ensuring reliable data storage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Oct 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.25%. Comparing base (7fa858c) to head (a24a371).
⚠️ Report is 71 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3828   +/-   ##
=======================================
  Coverage   92.25%   92.25%           
=======================================
  Files         115      115           
  Lines        9734     9734           
=======================================
  Hits         8980     8980           
  Misses        480      480           
  Partials      274      274           
Flag Coverage Δ
unittests 92.25% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds defensive copying of keys and values in the Set method of the memory storage implementation to prevent issues with sync.Pool reuse.

  • Copies both string keys and byte slice values before storing them in the map
  • Protects against unsafe memory reuse when strings/slices come from pooled resources
Comments suppressed due to low confidence (1)

internal/storage/memory/memory.go:90

  • The Delete method uses the key parameter directly without copying, which means it won't find entries stored with copied keys from Set. Since Set now stores entries using keyCopy, Delete should also copy the key: delete(s.db, utils.CopyString(key)) to ensure it can properly delete entries.
func (s *Storage) Delete(key string) error {
	// Ain't Nobody Got Time For That
	if len(key) == 0 {
		return nil
	}
	s.mux.Lock()
	delete(s.db, key)

Comment on lines +72 to +73
// Copy both key and value to avoid unsafe reuse from sync.Pool
keyCopy := utils.CopyString(key)
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Get method (line 57) returns v.data directly without copying, which could lead to the same sync.Pool reuse issues this change aims to prevent. If defensive copying is needed in Set, it should also be applied in Get to ensure data returned to callers cannot be unsafely modified or reused.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly fixes a critical memory corruption bug in the Set method by copying keys and values that may originate from a sync.Pool. The analysis of the problem is excellent.

While this is a great fix, I've found several other critical vulnerabilities in internal/storage/memory/memory.go that should be addressed to make the component safe for concurrent use.

  1. Unsafe Key Usage in Get and Delete: These methods are also vulnerable to memory corruption from pooled keys. The key should be copied before map access.

    // Example for Delete()
    keyCopy := utils.CopyString(key)
    s.mux.Lock()
    delete(s.db, keyCopy)
    s.mux.Unlock()
  2. Mutable Value Returned from Get: Get returns a direct reference to the internal byte slice, allowing callers to mutate stored data without a lock. It should return a copy.

    // Example for Get()
    // ... after finding entry v
    return utils.CopyBytes(v.data), nil
  3. Conn() Exposes Internal Map: The Conn() method returns the raw internal map, completely bypassing the mutex. This is extremely unsafe and will lead to race conditions and panics. This method should likely be removed or fundamentally redesigned to return a safe, deep copy of the data.

I highly recommend addressing these critical issues to ensure the stability and correctness of the memory storage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/storage/memory/memory.go (1)

72-78: LGTM! The defensive copying correctly prevents memory corruption.

The implementation properly addresses the sync.Pool corruption issue by copying both key and value before storage. The sequence is correct: early validation, copying outside the lock, then storing under lock.

Optional: Consider defensive copying in Get for consistency.

While not part of the current corruption issue, the Get method (line 57) returns v.data directly without copying. If callers mutate the returned slice, it affects internal storage. For complete defensive programming, consider:

 func (s *Storage) Get(key string) ([]byte, error) {
 	if len(key) == 0 {
 		return nil, nil
 	}
 	s.mux.RLock()
 	v, ok := s.db[key]
 	s.mux.RUnlock()
 	if !ok || v.expiry != 0 && v.expiry <= utils.Timestamp() {
 		return nil, nil
 	}
-
-	return v.data, nil
+	return utils.CopyBytes(v.data), nil
 }

This is a separate encapsulation concern, not the pooled-buffer issue this PR fixes, so it's fine to defer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fa858c and f6b5959.

📒 Files selected for processing (1)
  • internal/storage/memory/memory.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Apply formatting using gofumpt (Make target: format)
Optimize struct field alignment using betteralign (Make target: betteralign)
Modernize code using gopls modernize (Make target: modernize)

Files:

  • internal/storage/memory/memory.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Upload results
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: repeated
  • GitHub Check: unit (1.25.x, macos-13)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: Compare

Returning the stored slice directly allows callers to mutate the stored
data, which defeats the purpose of the defensive copying in Set().

This completes the fix by ensuring stored data cannot be corrupted
either on input (Set) or accessed mutably on output (Get).
@ReneWerner87 ReneWerner87 merged commit 6ef6de5 into gofiber:main Oct 31, 2025
15 of 16 checks passed
@sixcolors sixcolors deleted the fix/internal-memory-storage-pool-corruption branch November 2, 2025 15:17
@ReneWerner87 ReneWerner87 added this to v3 Nov 19, 2025
@ReneWerner87 ReneWerner87 added this to the v3 milestone Nov 19, 2025
@github-project-automation github-project-automation bot moved this to Done in v3 Nov 19, 2025
sixcolors added a commit that referenced this pull request Nov 21, 2025
This commit backports the defensive copying fixes from PR #3828 and #3829
to the v2 branch to prevent memory corruption from pooled buffers.

Changes:
1. internal/memory/memory.go:
   - Added defensive copying for string keys to prevent corruption from sync.Pool
   - Added defensive copying for []byte values on both Set and Get
   - Enhanced package documentation explaining safety guarantees
   - Optimized GC to skip locking when no items to delete

2. internal/storage/memory/memory.go:
   - Added defensive copying for string keys and []byte values in Set
   - Added defensive copying in Get to prevent external mutation
   - Updated method comments for clarity
   - Optimized GC to skip locking when no items to delete

3. middleware/csrf/storage_manager.go:
   - Removed redundant utils.CopyString since storage layer now handles it
   - Storage layer automatically handles defensive copying

4. middleware/idempotency/idempotency.go:
   - Removed redundant utils.CopyBytes before marshaling
   - Marshaling already creates new bytes, so pre-copy was redundant

These changes fix intermittent memory corruption issues when strings or
byte slices backed by sync.Pool buffers are stored in maps without copying.

Related PRs:
- #3828
- #3829
ReneWerner87 pushed a commit that referenced this pull request Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants