Skip to content

fix(idempotency): release MemoryLock keys on unlock (GHSA-c5qf-v26h-rp5r) - #4569

Merged
ReneWerner87 merged 1 commit into
v2from
fix/v2-idempotency-memorylock-leak
Jul 29, 2026
Merged

fix(idempotency): release MemoryLock keys on unlock (GHSA-c5qf-v26h-rp5r)#4569
ReneWerner87 merged 1 commit into
v2from
fix/v2-idempotency-memorylock-leak

Conversation

@ReneWerner87

Copy link
Copy Markdown
Member

Description

Backport of #3263 to v2.

MemoryLock.Unlock released the per-key mutex but never removed the map entry, so
every unique X-Idempotency-Key permanently retained one *sync.Mutex plus its key
string. An unauthenticated client can exhaust process memory by sending unique
36-character keys to any route behind idempotency.New(), since the default
KeyHeaderValidate checks length only.

Reported as GHSA-c5qf-v26h-rp5r (medium severity), affecting all v2 releases up to
v2.52.13. v3 has been fixed since #3263 (Dec 2024); v2 never received the backport.

Fix

Reference-count each key: Lock increments under l.mu, Unlock decrements and
deletes the entry only once the count reaches zero. A plain delete in Unlock
would be incorrect, because it drops an entry that other goroutines are still
blocked on. The logic is identical to the version shipped in v3.

Verification

The advisory PoC was run verbatim against both builds, 50,000 POSTs with unique keys:

build MemoryLock.keys retained heap delta after forced GC
v2.52.13 50000 286 B/key
this PR 0 187 B/key

The remaining 187 B/key is the response cache (cfg.Storage), which is TTL swept
(Lifetime 30 min, GCInterval 15 min), so it is bounded rather than leaked.

New regression test Test_MemoryLock_NoKeyLeak asserts the map drains to zero after
1000 sequential and 16x1000 contended lock/unlock cycles. The package passes under
-race.

Changes introduced

  • Bugfix: resolves a memory-exhaustion DoS in the idempotency middleware.
  • Tests: regression test covering sequential and contended key release.

…p5r)

MemoryLock.Unlock released the per-key mutex but never removed the map
entry, so every unique X-Idempotency-Key permanently retained ~286 bytes.
An unauthenticated client could exhaust process memory by sending unique
36-character keys to any route behind idempotency.New().

Backport of #3263: reference-count each key and delete the entry once the
last holder released it. A plain delete would drop entries still awaited
by blocked goroutines.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

MemoryLock now uses reference-counted per-key mutexes and removes entries when no holders or waiters remain. New internal tests verify cleanup across sequential and concurrent access.

Changes

MemoryLock key lifecycle

Layer / File(s) Summary
Counted lock lifecycle
middleware/idempotency/locker.go
Per-key locks now track usage counts, coordinate acquisition under the global mutex, and delete drained entries during unlock.
Key cleanup validation
middleware/idempotency/locker_internal_test.go
Tests verify that 1,000 sequential keys and repeated concurrent access leave the internal key map empty.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • gofiber/fiber#3263: Updates the same idempotency lock implementation with counted locks and key cleanup.

Suggested reviewers: gaby, sixcolors, efectn

Poem

A rabbit hopped through locks so neat,
Counting paws on every key.
When waiting ears had all withdrawn,
The empty map greeted dawn.
No stale carrots left to see!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the key change: releasing idempotency keys on unlock.
Description check ✅ Passed The description covers the bug, fix, verification, and regression test, matching the template's main intent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v2-idempotency-memorylock-leak

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@middleware/idempotency/locker_internal_test.go`:
- Around line 33-53: Update the contention test around the goroutine loop to
deterministically exercise a blocked Lock: acquire and hold a designated key,
start a second goroutine that attempts Lock on that key and signal when it
begins waiting, release the holder, then wait for the blocked goroutine to
complete and verify the locker's cleanup state. Preserve error reporting and
ensure all goroutines and locks are properly released.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a712369-7093-46f5-96c8-ba8a14486880

📥 Commits

Reviewing files that changed from the base of the PR and between bdc46e7 and 4d1617b.

📒 Files selected for processing (2)
  • middleware/idempotency/locker.go
  • middleware/idempotency/locker_internal_test.go

Comment thread middleware/idempotency/locker_internal_test.go
@ReneWerner87
ReneWerner87 merged commit a76710f into v2 Jul 29, 2026
20 checks passed
@ReneWerner87
ReneWerner87 deleted the fix/v2-idempotency-memorylock-leak branch July 29, 2026 20:46
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.

1 participant