PT-2026-51092 · Rubygems · Concurrent-Ruby
Publicado
2026-06-19
·
Atualizado
2026-06-19
·
CVE-2026-54906
CVSS v4.0
2.1
Baixa
| Vetor | AV:L/AC:H/AT:N/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N |
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:ruby
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:ruby
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:bash
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:
text
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)
Correção
Improper Locking
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Identificadores relacionados
Produtos afetados
Concurrent-Ruby