CVE-2026-54906: concurrent-ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption
Summary Concurrent::ReadWriteLock#releasewritelock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running.
Concurrent::ReadWriteLock#releasereadlock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError.
This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. It should not be framed as an authorization bypass; the lock is an in-process concurrency primitive, not an access-control boundary.
Version Software: concurrent-ruby Version: 1.3.6 Commit: 7a1b78941c081106c20a9ca0144ac73a48d254ab
Details
releasewritelock checks only whether the global counter indicates that a writer is running. It does not track or verify ownership:
ruby def releasewritelock return true unless runningwriter? c = @Counter.update { |counter| counter - RUNNINGWRITER } @ReadLock.broadcast @WriteLock.signal if waitingwriters(c) > 0 true end
Because ownership is not checked, a different thread can clear the RUNNINGWRITER bit while the original writer is still inside its critical section. Another writer can then acquire the write lock and run concurrently with the first writer.
releasereadlock unconditionally decrements the shared counter:
ruby def releasereadlock while true c = @Counter.value if @Counter.compareandset(c, c-1) if waitingwriter?(c) && runningreaders(c) == 1 @WriteLock.signal end break end end true end
On a fresh lock, this changes the counter from 0 to -1. A later acquirereadlock raises Concurrent::ResourceLimitError because the maximum-reader check masks the negative counter as saturated.
Reproduce
From the root of a concurrent-ruby checkout, run:
bash ruby -Ilib/concurrent-ruby - <<'RUBY' require 'concurrent/atomic/readwritelock' require 'concurrent/version' require 'thread'
puts "ruby=#{RUBYDESCRIPTION}" puts "concurrentrubyversion=#{Concurrent::VERSION}" puts "poc=ReadWriteLock release methods corrupt or bypass lock state"
lock = Concurrent::ReadWriteLock.new events = Queue.new writer1inside = false
writer1 = Thread.new do lock.acquirewritelock writer1inside = true events << :writer1acquired sleep 0.5 writer1inside = false lock.releasewritelock events << :writer1finished end
events.pop puts 'writer1acquired=true'
intruderresult = nil intruder = Thread.new do intruderresult = lock.releasewritelock end intruder.join
puts "wrongthreadreleasewritelockreturned=#{intruderresult}"
writer2enteredwhilewriter1inside = nil writer2 = Thread.new do lock.acquirewritelock writer2enteredwhilewriter1inside = writer1inside lock.releasewritelock end
writer2.join(0.25)
puts "writer2acquiredwhilewriter1inside=#{writer2enteredwhilewriter1inside}"
writer1.join
lock2 = Concurrent::ReadWriteLock.new strayreadreleaseresult = lock2.releasereadlock counterafterstrayreadrelease = lock2.instanceeval { @Counter.value } readafterstrayrelease = begin lock2.acquirereadlock 'acquired' rescue => error "#{error.class}: #{error.message}" end
puts "strayreleasereadlockreturned=#{strayreadreleaseresult}" puts "counterafterstrayreadrelease=#{counterafterstrayreadrelease}" puts "acquirereadafterstrayrelease=#{readafterstrayrelease}"
if intruderresult && writer2enteredwhilewriter1inside && counterafterstrayreadrelease == -1 puts 'result=REPRODUCED wrong-thread write release and stray read-release corruption' else puts 'result=NOTREPRODUCED' end Expected result:
- A second thread successfully calls releasewritelock while the first writer still holds the lock. - A second writer enters while the first writer is still inside the write critical section. - Calling releasereadlock on a fresh lock changes the counter to -1. - A subsequent read acquisition fails with Concurrent::ResourceLimitError.
Log evidence
Local reproduction output:
text ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25] concurrentrubyversion=1.3.6 poc=ReadWriteLock release methods corrupt or bypass lock state writer1acquired=true wrongthreadreleasewritelockreturned=true writer2acquiredwhilewriter1inside=true strayreleasereadlockreturned=true counterafterstrayreadrelease=-1 acquirereadafterstrayrelease=Concurrent::ResourceLimitError: Too many reader threads result=REPRODUCED wrong-thread write release and stray read-release corruption
Impact This can break the write-lock mutual exclusion guarantee and can also leave a lock unusable after a stray read release. The impact is local to applications that expose or misuse the manual acquire / release APIs. If the lock protects integrity-sensitive mutable state, wrong-thread write release can allow concurrent writers and data races. The stray read-release path can cause denial of service by corrupting the lock counter.
Credit Pranjali Thakur - depthfirst (depthfirst.com)
Other sources
concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReadWriteLock#releasewritelock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running. Concurrent::ReadWriteLock#releasereadlock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError. This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. This vulnerability is fixed in 1.3.7.
— MITRE
concurrent-ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption
— Microsoft
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
rubygems/concurrent-rubyto a version that resolves this vulnerability.Fixed in 1.3.7 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 1.3.7-1 - Upgrade
Upgrade
concurrent-ruby/Concurrent::ReadWriteLockto a version that resolves this vulnerability.Fixed in 1.3.7
Event History
Frequently Asked Questions
What is the severity of CVE-2026-54906?
The severity of CVE-2026-54906 is rated as 40.
What is the main issue in CVE-2026-54906?
CVE-2026-54906 allows any thread to release an active write lock held by another thread, leading to potential data corruption.
How do I fix CVE-2026-54906?
To fix CVE-2026-54906, ensure that the write lock is properly verified by the calling thread before it is released.
Which software is affected by CVE-2026-54906?
CVE-2026-54906 affects the concurrent-ruby gem in RubyGems.
When was CVE-2026-54906 published?
CVE-2026-54906 was published on June 19, 2026.