Skip to content

CVE-2026-54906 (Medium) detected in concurrent-ruby-1.3.5.gem #290

Description

@mend-bolt-for-github

CVE-2026-54906 - Medium Severity Vulnerability

Vulnerable Library - concurrent-ruby-1.3.5.gem

Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more. Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns.

Library home page: https://rubygems.org/gems/concurrent-ruby-1.3.5.gem

Path to dependency file: /Gemfile.lock

Path to vulnerable library: /vendor/cache/concurrent-ruby-1.3.5.gem

Dependency Hierarchy:

  • manageiq-style-1.3.3.gem (Root Library)
    • more_core_extensions-4.5.1.gem
      • activesupport-8.0.2.gem
        • concurrent-ruby-1.3.5.gem (Vulnerable Library)

Found in base branch: master

Vulnerability Details

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" (http://depthfirst.com))

Publish Date: 2026-06-19

URL: CVE-2026-54906

CVSS 3 Score Details (4.0)

Base Score Metrics:

  • Exploitability Metrics:
    • Attack Vector: Local
    • Attack Complexity: High
    • Privileges Required: None
    • User Interaction: None
    • Scope: Unchanged
  • Impact Metrics:
    • Confidentiality Impact: None
    • Integrity Impact: Low
    • Availability Impact: Low

For more information on CVSS3 Scores, click here.

Suggested Fix

Type: Upgrade version

Origin: GHSA-6wx8-w4f5-wwcr

Release Date: 2026-06-19

Fix Resolution: concurrent-ruby - 1.3.7


Step up your Open Source Security Game with Mend here

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions