Every CVE Search/ Get API key

CVE-2026-54905

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

2.0 LowFix available

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Attack profile

How easy it is to exploit

Attack vector
Local
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
Low
Integrity
Low
Availability
Low

Exploit likelihood

0.15%Unchanged since Jun 25, 2026
90%50%10%1%

What each source reports

CVE List Score used 2.0

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

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
CVE-2026-54905
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
Weakness
CWE-128
Updated
2026-11-02
EUVDEUVD-2026-39000 2.0

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
EUVD-2026-39000
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-wv3x-4vxv-whpp 2.0

Concurrent Ruby: `ReentrantReadWriteLock` read-count overflow grants a write lock without exclusivity

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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:

READER_BITS    = 15
WRITER_BITS    = 14

WAITING_WRITER = 1 << READER_BITS
RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
MAX_READERS    = WAITING_WRITER - 1
MAX_WRITERS    = RUNNING_WRITER - MAX_READERS - 1

WRITE_LOCK_HELD = 1 << READER_BITS
READ_LOCK_MASK  = WRITE_LOCK_HELD - 1
WRITE_LOCK_MASK = MAX_WRITERS

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

if (held = @HeldCount.value) > 0
  if held & READ_LOCK_MASK == 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 WRITE_LOCK_HELD. Then try_write_lock returns success through its "already have a write lock" branch:

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

This branch does not set the global RUNNING_WRITER 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

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

require 'concurrent/atomic/reentrant_read_write_lock'
require 'concurrent/version'
require 'thread'

def wait_for_queue(queue, timeout_seconds)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
  loop do
    return queue.pop(true)
  rescue ThreadError
    return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline

    sleep 0.001
  end
end

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

lock = Concurrent::ReentrantReadWriteLock.new
other_reader_ready = Queue.new
other_reader_stop = Queue.new

other_reader = Thread.new do
  lock.acquire_read_lock
  other_reader_ready << :held
  other_reader_stop.pop
end

wait_for_queue(other_reader_ready, 1)
puts "other_thread_holds_read_lock=true"

depth = Concurrent::ReentrantReadWriteLock::WRITE_LOCK_HELD
depth.times { lock.acquire_read_lock }

held_count = lock.instance_eval { @HeldCount.value }
counter_before = lock.instance_eval { @Counter.value }

puts "main_thread_read_acquisitions=#{depth}"
puts "main_thread_held_count=#{held_count}"
puts "counter_before_try_write=#{counter_before}"
puts "running_writer_bit_before=#{(counter_before & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

write_granted = lock.try_write_lock
counter_after = lock.instance_eval { @Counter.value }

puts "try_write_lock_returned=#{write_granted}"
puts "counter_after_try_write=#{counter_after}"
puts "running_writer_bit_after=#{(counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

third_reader_ready = Queue.new
third_reader = Thread.new do
  lock.acquire_read_lock
  third_reader_ready << :acquired
end

third_reader_acquired =…
Record
GHSA-wv3x-4vxv-whpp
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
CVSS 3.1
5.5 MediumCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-128
Updated
2026-11-02
NVD 2.0

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
CVE-2026-54905
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
5.5 MediumCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-128
Updated
2026-11-02
OSV 2.0

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

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
CVE-2026-54905
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
Weakness
CWE-128
Updated
2026-11-02
OSVDEBIAN-CVE-2026-54905 5.5

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
DEBIAN-CVE-2026-54905
CVSS 3.1
5.5 MediumCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Updated
2026-11-02
OSVGHSA-wv3x-4vxv-whpp 2.0

Concurrent Ruby: `ReentrantReadWriteLock` read-count overflow grants a write lock without exclusivity

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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:

READER_BITS    = 15
WRITER_BITS    = 14

WAITING_WRITER = 1 << READER_BITS
RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
MAX_READERS    = WAITING_WRITER - 1
MAX_WRITERS    = RUNNING_WRITER - MAX_READERS - 1

WRITE_LOCK_HELD = 1 << READER_BITS
READ_LOCK_MASK  = WRITE_LOCK_HELD - 1
WRITE_LOCK_MASK = MAX_WRITERS

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

if (held = @HeldCount.value) > 0
  if held & READ_LOCK_MASK == 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 WRITE_LOCK_HELD. Then try_write_lock returns success through its "already have a write lock" branch:

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

This branch does not set the global RUNNING_WRITER 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

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

require 'concurrent/atomic/reentrant_read_write_lock'
require 'concurrent/version'
require 'thread'

def wait_for_queue(queue, timeout_seconds)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
  loop do
    return queue.pop(true)
  rescue ThreadError
    return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline

    sleep 0.001
  end
end

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

lock = Concurrent::ReentrantReadWriteLock.new
other_reader_ready = Queue.new
other_reader_stop = Queue.new

other_reader = Thread.new do
  lock.acquire_read_lock
  other_reader_ready << :held
  other_reader_stop.pop
end

wait_for_queue(other_reader_ready, 1)
puts "other_thread_holds_read_lock=true"

depth = Concurrent::ReentrantReadWriteLock::WRITE_LOCK_HELD
depth.times { lock.acquire_read_lock }

held_count = lock.instance_eval { @HeldCount.value }
counter_before = lock.instance_eval { @Counter.value }

puts "main_thread_read_acquisitions=#{depth}"
puts "main_thread_held_count=#{held_count}"
puts "counter_before_try_write=#{counter_before}"
puts "running_writer_bit_before=#{(counter_before & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

write_granted = lock.try_write_lock
counter_after = lock.instance_eval { @Counter.value }

puts "try_write_lock_returned=#{write_granted}"
puts "counter_after_try_write=#{counter_after}"
puts "running_writer_bit_after=#{(counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

third_reader_ready = Queue.new
third_reader = Thread.new do
  lock.acquire_read_lock
  third_reader_ready << :acquired
end

third_reader_acquired =…
Record
GHSA-wv3x-4vxv-whpp
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
CVSS 3.1
5.5 MediumCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weakness
CWE-128
Updated
2026-11-02
OSVUBUNTU-CVE-2026-54905 2.0

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
UBUNTU-CVE-2026-54905
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N
CVSS 3.1
5.5 MediumCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Updated
2026-11-02
OSVAZL-91290 No score

CVE-2026-54905 affecting package rubygem-concurrent-ruby for versions less than 1.3.7-1

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 WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER 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.

Record
AZL-91290
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
concurrent-ruby
RubyGems
before 1.3.7
1.3.7
rubygem-concurrent-ruby
Azure Linux:3
before 1.3.7-1
1.3.7-1
ruby-concurrent
Debian:12
All versions, plus listed versions
1.1.6+dfsg-51.2.3-11.2.3-21.2.3-31.2.3-4
Show 9 moreShow less
1.3.4-11.3.4-1~exp11.3.5-11.3.6-11.3.7-11.3.7-21.3.7-31.3.7-41.3.8-1
None known
ruby-concurrent
Debian:13
All versions, plus listed versions
1.3.5-11.3.6-11.3.7-11.3.7-21.3.7-31.3.7-41.3.8-1
None known
ruby-concurrent
Debian:14
before 1.3.7-1, plus listed versions
1.3.5-11.3.6-1
1.3.7-1
concurrent-ruby
RubyGems
before 1.3.7, plus listed versions
0.0.10.1.00.1.10.1.1.pre.10.1.1.pre.2
Show 45 moreShow less
0.1.1.pre.30.1.1.pre.40.1.1.pre.50.2.00.2.10.2.20.3.00.3.0.pre.10.3.0.pre.20.3.0.pre.30.3.10.3.1.pre.10.3.1.pre.20.3.20.4.00.4.10.5.00.5.0.pre.10.6.00.6.0.pre.10.6.0.pre.20.6.10.7.00.7.0.rc00.7.0.rc10.7.0.rc20.7.10.7.20.8.00.8.0.pre10.8.0.pre20.9.00.9.0.pre20.9.0.pre30.9.10.9.21.0.01.0.0.pre11.0.0.pre21.0.0.pre31.0.0.pre41.0.0.pre51.0.11.0.21.0.3
1.3.7
ruby-concurrent
Ubuntu:16.04:LTS
All versions, plus listed versions
1.0.0-1
None known
ruby-concurrent
Ubuntu:18.04:LTS
All versions, plus listed versions
1.0.5-1
None known
ruby-concurrent
Ubuntu:20.04:LTS
All versions, plus listed versions
1.0.5-31.0.5-41.1.6+dfsg-21.1.6+dfsg-3
None known
ruby-concurrent
Ubuntu:22.04:LTS
All versions, plus listed versions
1.1.6+dfsg-31.1.6+dfsg-3build11.1.6+dfsg-4
None known
Show 4 moreShow less
Affected packages
PackageAffectedFixed
ruby-concurrent
Ubuntu:24.04:LTS
All versions, plus listed versions
1.1.6+dfsg-5build21.1.6+dfsg-5build31.2.3-2build1
None known
ruby-concurrent
Ubuntu:25.10
All versions, plus listed versions
1.3.4-1
None known
ruby-concurrent
Ubuntu:26.10
All versions, plus listed versions
1.3.6-11.3.7-41.3.8-1
None known
ruby-concurrent
Ubuntu:26.04:LTS
All versions, plus listed versions
1.3.4-11.3.6-1
None known

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-54905 is a low vulnerability (CVSS 2.0) in Ruby-concurrency Concurrent-ruby, published June 19, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.15%, the 4th percentile. Fixed in concurrent-ruby 1.3.7.

References

Show 4 moreShow less
This page doesn't list everything

Left out here: 27 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open