Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
💎 RubyGems
Not in CISA KEV

GHSA-6wx8-w4f5-wwcr

GHSA-6wx8-w4f5-wwcr is a CWE-414 vulnerability in concurrent-ruby. O3 Security confirms whether GHSA-6wx8-w4f5-wwcr is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Concurrent Ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption

Also known asCVE-2026-54906
Published
Jun 19, 2026
Updated
Jun 21, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Jun 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
💎concurrent-ruby

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Concurrent::ReadWriteLock#release_write_lock 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#release_read_lock 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

release_write_lock checks only whether the global counter indicates that a writer is running. It does not track or verify ownership:

def release_write_lock
  return true unless running_writer?
  c = @Counter.update { |counter| counter - RUNNING_WRITER }
  @ReadLock.broadcast
  @WriteLock.signal if waiting_writers(c) > 0
  true
end

Because ownership is not checked, a different thread can clear the RUNNING_WRITER 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.

release_read_lock unconditionally decrements the shared counter:

def release_read_lock
  while true
    c = @Counter.value
    if @Counter.compare_and_set(c, c-1)
      if waiting_writer?(c) && running_readers(c) == 1
        @WriteLock.signal
      end
      break
    end
  end
  true
end

On a fresh lock, this changes the counter from 0 to -1. A later acquire_read_lock raises Concurrent::ResourceLimitError because the maximum-reader check masks the negative counter as saturated.

Reproduce

From the root of a concurrent-ruby checkout, run:

ruby -Ilib/concurrent-ruby - <<'RUBY'
require 'concurrent/atomic/read_write_lock'
require 'concurrent/version'
require 'thread'

puts "ruby=#{RUBY_DESCRIPTION}"
puts "concurrent_ruby_version=#{Concurrent::VERSION}"
puts "poc=ReadWriteLock release methods corrupt or bypass lock state"

lock = Concurrent::ReadWriteLock.new
events = Queue.new
writer1_inside = false

writer1 = Thread.new do
  lock.acquire_write_lock
  writer1_inside = true
  events << :writer1_acquired
  sleep 0.5
  writer1_inside = false
  lock.release_write_lock
  events << :writer1_finished
end

events.pop
puts 'writer1_acquired=true'

intruder_result = nil
intruder = Thread.new do
  intruder_result = lock.release_write_lock
end
intruder.join

puts "wrong_thread_release_write_lock_returned=#{intruder_result}"

writer2_entered_while_writer1_inside = nil
writer2 = Thread.new do
  lock.acquire_write_lock
  writer2_entered_while_writer1_inside = writer1_inside
  lock.release_write_lock
end

writer2.join(0.25)

puts "writer2_acquired_while_writer1_inside=#{writer2_entered_while_writer1_inside}"

writer1.join

lock2 = Concurrent::ReadWriteLock.new
stray_read_release_result = lock2.release_read_lock
counter_after_stray_read_release = lock2.instance_eval { @Counter.value }
read_after_stray_release = begin
  lock2.acquire_read_lock
  'acquired'
rescue => error
  "#{error.class}: #{error.message}"
end

puts "stray_release_read_lock_returned=#{stray_read_release_result}"
puts "counter_after_stray_read_release=#{counter_after_stray_read_release}"
puts "acquire_read_after_stray_release=#{read_after_stray_release}"

if intruder_result && writer2_entered_while_writer1_inside && counter_after_stray_read_release == -1
  puts 'result=REPRODUCED wrong-thread write release and stray read-release corruption'
else
  puts 'result=NOT_REPRODUCED'
end

Expected result:

  • A second thread successfully calls release_write_lock while the first writer still holds the lock.
  • A second writer enters while the first writer is still inside the write critical section.
  • Calling release_read_lock on a fresh lock changes the counter to -1.
  • A subsequent read acquisition fails with Concurrent::ResourceLimitError.

Log evidence

Local reproduction output:

ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
concurrent_ruby_version=1.3.6
poc=ReadWriteLock release methods corrupt or bypass lock state
writer1_acquired=true
wrong_thread_release_write_lock_returned=true
writer2_acquired_while_writer1_inside=true
stray_release_read_lock_returned=true
counter_after_stray_read_release=-1
acquire_read_after_stray_release=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)

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
💎RubyGemsconcurrent-rubyall versions1.3.7

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for concurrent-ruby. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update concurrent-ruby to 1.3.7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-6wx8-w4f5-wwcr is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-6wx8-w4f5-wwcr is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-6wx8-w4f5-wwcr. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary `Concurrent::ReadWriteLock#release_write_lock` 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#release_read_lock` 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 c
O3 Security · Impact-Aware SCA

Is GHSA-6wx8-w4f5-wwcr in your dependencies?

O3 detects GHSA-6wx8-w4f5-wwcr across RubyGems dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-6wx8-w4f5-wwcr: concurrent-ruby… | O3 Security