Skip to content

🐛 Backport defensive copying fixes from #3828 and #3829 to v2#3888

Merged
ReneWerner87 merged 1 commit intov2from
backport-defensive-copying-v2
Nov 21, 2025
Merged

🐛 Backport defensive copying fixes from #3828 and #3829 to v2#3888
ReneWerner87 merged 1 commit intov2from
backport-defensive-copying-v2

Conversation

@sixcolors
Copy link
Member

Description

This PR backports the defensive copying fixes from PR #3828 and #3829 to the v2 branch to prevent memory corruption from pooled buffers.

Background

PR #3828 and #3829 fixed critical memory corruption issues in the main branch where strings or byte slices backed by sync.Pool buffers were stored in maps without copying, causing intermittent data corruption when buffers were reused.

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

Why This Matters

The Problem

Fiber uses sync.Pool for buffer reuse. When strings or []byte slices backed by pooled buffers are stored directly in maps without copying:

  1. The map holds references to pooled memory
  2. When buffers return to the pool and get reused
  3. Map keys/values become corrupted
  4. Results in intermittent data loss and failures

The Solution

Defensive copying at the storage layer ensures:

  • All stored data is independent of pooled buffers
  • Middleware doesn't need to worry about pooling safety
  • Centralized fix prevents future issues

Performance Impact

  • Minimal: Only affects string keys and []byte values
  • Structs, integers, and other types remain zero-copy
  • Trade-off: Small allocation overhead for correctness and safety

Testing

✅ All memory storage tests pass (internal/memory and internal/storage/memory)
✅ All CSRF middleware tests pass
✅ All idempotency middleware tests pass

Related PRs

Breaking Changes

None - this is a bug fix that makes storage implementations behave correctly.

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
@sixcolors sixcolors requested a review from a team as a code owner November 21, 2025 00:55
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

The PR enhances the in-memory storage layer by implementing defensive copying for both keys and values in Get/Set operations to prevent external mutations. Additionally, a GC optimization skips locking when no keys expire, and dependent middleware layers are adjusted to leverage the storage layer's copying behavior instead of duplicating the work.

Changes

Cohort / File(s) Summary
Memory Storage Defensive Copying
internal/memory/memory.go, internal/storage/memory/memory.go
Implemented defensive copying of string keys and []byte values in Set; Get now returns copies of byte slices. Reordered struct fields and expanded documentation on mutation safety and TTL semantics. Added GC optimization to skip locking when no expired keys exist.
Middleware Adjustments
middleware/csrf/storage_manager.go
Removed defensive copy of keys in setRaw for in-memory storage path; relies on storage layer to handle copying.
Idempotency Response Handling
middleware/idempotency/idempotency.go
Changed response body assignment from defensive copy (utils.CopyBytes) to direct reference to original body.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Middleware as Middleware
    participant Storage as In-Memory Storage
    participant Memory as Internal Memory

    rect rgb(230, 240, 250)
        Note over Middleware,Storage: Set Operation (Defensive Copy)
        Middleware->>Storage: Set(key, value)
        Storage->>Memory: CopyString(key)
        Storage->>Memory: CopyBytes(value)
        Memory->>Memory: Store(copied_key, copied_value)
    end

    rect rgb(240, 230, 250)
        Note over Middleware,Storage: Get Operation (Defensive Copy)
        Middleware->>Storage: Get(key)
        Storage->>Memory: Retrieve(key)
        Memory->>Storage: CopyBytes(stored_value)
        Storage->>Middleware: Return copied_value
    end

    rect rgb(250, 240, 230)
        Note over Storage,Memory: GC Optimization
        Storage->>Memory: gc()
        alt No Expired Keys
            Memory->>Storage: Return (skip locking)
        else Expired Keys Found
            Memory->>Storage: Lock → Cleanup → Unlock
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Key areas requiring attention:
    • Verify defensive copy implementations in Get/Set are correct for both string keys and byte slice values
    • Confirm GC optimization logic properly detects and handles the no-expiry case without race conditions
    • Review idempotency middleware's removal of defensive copy on response body—validate this doesn't reintroduce mutation risks given the storage layer's new copy guarantees
    • Ensure all call sites (CSRF middleware) correctly rely on storage-level copying without duplicating the work

Possibly related PRs

  • PR #3829: Makes identical defensive copying changes to internal memory storage and adjusts middleware to rely on storage-level copying
  • PR #3828: Modifies internal/storage/memory/memory.go with the same defensive copy pattern for keys and values to prevent pooled-buffer corruption

Suggested labels

☢️ Bug

Suggested reviewers

  • gaby
  • ReneWerner87
  • efectn

Poem

🐰 A rabbit hops through memory's halls,
Defensive copies guard us all,
No mutations leak to the side,
Pooled buffers safe, nowhere to hide!
Keys and values copied with care,
Garbage-collected without a snare! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title references the backported fixes and their source PRs, clearly summarizing the main change to implement defensive copying in v2 branch.
Description check ✅ Passed The description is comprehensive and well-structured, covering the background, detailed changes across all four files, problem explanation, solution, performance impact, and testing status.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch backport-defensive-copying-v2

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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 backports critical defensive copying fixes to the v2 branch, addressing potential memory corruption issues. By ensuring that string keys and byte slice values are copied when stored in internal memory maps, it prevents data integrity problems that could arise from the reuse of sync.Pool buffers. This change centralizes memory safety within the storage layers, simplifying middleware logic and enhancing the overall stability of the application without introducing breaking changes.

Highlights

  • Memory Safety Enhancement: Implemented defensive copying for string keys and byte slice values in internal memory storage mechanisms to prevent data corruption stemming from sync.Pool buffer reuse.
  • Storage Layer Responsibility: Centralized defensive copying logic within the internal/memory and internal/storage/memory packages, offloading this concern from middleware.
  • Middleware Simplification: Removed redundant copying operations in middleware/csrf and middleware/idempotency as the underlying storage now handles these safety measures.
  • Performance Optimization: Optimized garbage collection routines in memory storage implementations to avoid unnecessary locking when no items need to be deleted.
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.

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 effectively backports critical defensive copying fixes to prevent memory corruption from pooled buffers. The changes are well-implemented across the internal/memory and internal/storage/memory packages, correctly adding defensive copies for keys and values to ensure data integrity. The accompanying documentation updates are excellent, clearly outlining the safety guarantees. Furthermore, the removal of redundant copy operations in the middleware and the garbage collection optimization are valuable improvements. Overall, this is a high-quality and important contribution. I have one minor suggestion to improve comment clarity.

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 (2)
internal/storage/memory/memory.go (2)

108-113: Consider making Close idempotent to avoid potential blocking on repeated calls

Close sends once on an unbuffered done channel and assumes it’s only ever called a single time; a second call would block indefinitely if the GC goroutine has already exited. If there’s any chance of multiple Close calls, consider guarding with a select/default, a sync.Once, or closing the channel instead of sending.


154-159: Conn documentation clarifies mutability constraints but concurrency caveat remains

The updated comment makes it explicit that callers must not modify the returned map, which is important given the live backing db. If Conn is ever used outside single-threaded test scenarios, you might consider returning a shallow copy instead to avoid accidental concurrent map access.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e334f54 and 84e961e.

📒 Files selected for processing (4)
  • internal/memory/memory.go (4 hunks)
  • internal/storage/memory/memory.go (7 hunks)
  • middleware/csrf/storage_manager.go (1 hunks)
  • middleware/idempotency/idempotency.go (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-28T17:39:26.644Z
Learnt from: arturmelanchyk
Repo: gofiber/fiber PR: 3768
File: client/request.go:162-166
Timestamp: 2025-09-28T17:39:26.644Z
Learning: In fasthttp-based code like gofiber, using utils.UnsafeString(key) instead of string(key) is acceptable when values are used immediately within the same call stack and not stored for later use, as it avoids allocations and aligns with fasthttp's zero-allocation design philosophy.

Applied to files:

  • middleware/csrf/storage_manager.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • middleware/csrf/storage_manager.go
🧬 Code graph analysis (3)
internal/memory/memory.go (3)
internal/storage/memory/memory.go (1)
  • Storage (14-19)
utils/time.go (1)
  • Timestamp (13-13)
utils/convert.go (2)
  • CopyBytes (21-25)
  • CopyString (16-18)
middleware/idempotency/idempotency.go (1)
client.go (1)
  • Response (38-38)
internal/storage/memory/memory.go (1)
utils/convert.go (2)
  • CopyBytes (21-25)
  • CopyString (16-18)
🔇 Additional comments (14)
middleware/csrf/storage_manager.go (1)

57-58: Delegating key/value copying to the memory storage is correct

internal/memory.Storage.Set now defensively copies both the string key and []byte value, so dropping the extra utils.CopyString here is safe and removes redundant work. The comment accurately documents that this branch relies on the storage layer for defensive copying.

middleware/idempotency/idempotency.go (1)

120-120: Using the response body slice directly is safe in this context

Here the body slice is only used to build the msgpack-encoded bs in the same call and is never stored itself; only bs is written to cfg.Storage. This makes the extra defensive copy unnecessary while keeping the cached data independent of fasthttp’s internal buffers.

internal/storage/memory/memory.go (5)

46-61: Get now safely isolates stored byte slices from callers

The updated Get method correctly treats missing/expired entries as nil and returns a CopyBytes clone of v.data, preventing callers from mutating the underlying storage buffer. This aligns with the defensive-copying goal without changing the external API.


63-83: Set’s defensive copying of key and value fixes sync.Pool aliasing risks

Copying the string key with CopyString and the []byte value with CopyBytes before inserting into db ensures neither map keys nor values can reference sync.Pool-backed buffers or other mutable slices. TTL computation and locking behavior remain consistent with the previous implementation.


87-97: Delete comment now matches behavior

The new comment accurately states that Delete removes the value stored for the key; implementation is unchanged and still correctly guards with a cheap key-length check and a write lock.


99-106: Reset comment clarifies full map wipe semantics

The comment now explicitly documents that all keys and values are cleared by swapping in a new map under lock, which matches the existing implementation.


135-138: GC fast-path correctly avoids unnecessary locking

The len(expired) == 0 check allows skipping the write lock when nothing needs deletion, while the subsequent double-checked deletion under s.mux.Lock() preserves correctness. This is a safe and useful contention reduction.

internal/memory/memory.go (7)

1-16: Package-level documentation accurately describes safety and usage

The expanded comment clearly explains defensive copying for string keys and []byte values, and properly scopes responsibility for other pointer/slice types. This makes the storage’s guarantees and limitations much clearer to internal callers.


34-35: Expiry field comment remains accurate and helps bound TTL expectations

Documenting the uint32 max timestamp is useful context for anyone configuring very long TTLs, and it still matches how e is computed and compared in GC.


47-66: Get’s byte-slice copy strikes a good balance between safety and performance

Returning nil for missing/expired entries preserves existing semantics, while only copying when the stored value is a []byte prevents corruption from external mutation without penalizing other value types. The atomic timestamp check integrates cleanly with the existing GC scheme.


68-92: Set correctly centralizes defensive copying for keys and byte values

Copying the string key via CopyString and cloning []byte values before storage ensures no map entry can reference sync.Pool-backed or otherwise mutable buffers, which directly addresses the original corruption issue. TTL handling (positive => expiring, non-positive => forever) remains consistent with the new documentation.


94-99: Delete documentation now matches its straightforward behavior

The comment now explicitly states that the key and its value are removed under a write lock, which matches the implementation and helps make the API clearer.


101-107: Reset behavior is clearly documented and implementation is sound

Swapping in a fresh map under lock is an efficient way to drop all entries, and the doc comment now makes this “clear all keys” behavior explicit.


125-128: GC optimization avoids unnecessary locking when there’s nothing to purge

The len(expired) == 0 check reuses the collected expired slice and safely skips the write lock when no items are due for deletion, keeping the double-checked removal logic intact for non-empty cases.

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@ReneWerner87 ReneWerner87 merged commit 0786b39 into v2 Nov 21, 2025
33 of 34 checks passed
@ReneWerner87 ReneWerner87 deleted the backport-defensive-copying-v2 branch November 21, 2025 09:59
@ReneWerner87 ReneWerner87 added this to the v2.52.11 milestone Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants