PT-2026-51090 · Rubygems · Concurrent-Ruby
Published
2026-06-19
·
Updated
2026-06-19
·
CVE-2026-54904
CVSS v4.0
8.2
High
| Vector | AV: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 untilcompare and set(old value, new value)succeeds.- Numeric
compare and set, which checksold == old valuebefore attempting the underlying atomic swap. - Ruby NaN semantics, where
Float::NAN == Float::NANis alwaysfalse.
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
endFor 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
endWhen 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.0Impact
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
Affected Products
Concurrent-Ruby