CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-126072

CVE-2026-126072: Thread Deadlock and Denial of Service in Ruby on Rails ActionController::Live

Amit Schendel
Amit Schendel
Senior Security Researcher

Nov 13, 2026·10 min read·2 visits

Executive Summary (TL;DR)

A resource lock-contention deadlock (CWE-833) in Ruby on Rails' ActionController::Live::Buffer allows unauthenticated remote attackers to permanently hang web server worker threads and controller producer threads by ceasing TCP reads on streaming endpoints. Upgrade to Rails 8.1.4.1 or 8.0.5.2.

CVE-2026-126072 (GHSA-rwvh-fw6r-pwvp) is a concurrency deadlock vulnerability in the Ruby on Rails Action Pack framework affecting applications that utilize ActionController::Live response streaming. The defect occurs due to improper synchronization within ActionController::Live::Buffer when handling full bounded queues during stream closure and connection abortion. An unauthenticated remote attacker can trigger this condition by stalling TCP socket reads on streaming endpoints—including Active Storage proxy mode—resulting in permanent worker thread exhaustion and application-wide denial of service.

Vulnerability Overview

Ruby on Rails provides the ActionController::Live module within the Action Pack (actionpack) gem to support persistent HTTP response streaming. This capability allows controller actions to transmit data chunks incrementally over an open HTTP connection rather than buffering the entire payload in memory prior to transmission. Beyond custom streaming controllers, core Rails features rely directly on this mechanism, including ActionController::DataStreaming#send_stream and Active Storage's proxy delivery mode (ActiveStorage::Blobs::ProxyController).

To decouple controller execution from network I/O latency, ActionController::Live operates using a multi-threaded producer-consumer architecture. A spawned producer thread executes the controller action and pushes data chunks into an internal buffer (ActionController::Live::Buffer), while the underlying Rack web server thread (such as a Puma worker thread) acts as the consumer, reading chunks from the buffer and writing them to the client TCP socket.

CVE-2026-126072 (also tracked as GHSA-rwvh-fw6r-pwvp) is a synchronization deadlock (CWE-833) in ActionController::Live::Buffer affecting Rails versions prior to 8.0.5.2 and 8.1.4.1. The vulnerability arises when the internal bounded queue reaches capacity due to a slow or stalled client connection, and the producer thread attempts to close the stream while holding the buffer's mutex lock.

When the web server subsequently detects a socket write timeout or client disconnection and invokes the buffer's abort routine, the consumer thread blocks indefinitely waiting to acquire the mutex held by the stalled producer thread. Because neither thread can proceed, both the controller producer thread and the web server worker thread are permanently leaked. Repeatedly triggering this state across concurrent connections exhausts the application server's thread pool, resulting in a complete Denial of Service (DoS).

Root Cause Analysis

The root cause of CVE-2026-126072 lies in the interaction between two synchronization primitives inside ActionController::Live::Buffer: a reentrant monitor lock provided by Ruby's MonitorMixin (synchronize) and a bounded thread-safe queue implemented via Thread::SizedQueue (@buf). The SizedQueue enforces backpressure on the producer thread so that if a client reads slowly from the socket, the controller thread pauses once the queue reaches its configured slot limit rather than consuming unbounded memory.

In vulnerable versions of Rails, stream termination is signaled by pushing a nil sentinel object onto @buf inside ActionController::Live::Buffer#close. Crucially, Buffer#close acquires the MonitorMixin lock via synchronize do ... end before executing @buf.push nil. When @buf is already at full capacity because the client has ceased reading from the TCP socket, the @buf.push nil call blocks the producer thread until a consumer thread removes an item from the queue. Because this blocking operation occurs inside the synchronize block, the producer thread retains ownership of the monitor lock while asleep.

While the producer thread is blocked inside Buffer#close, the consumer thread (the web server worker) eventually encounters a socket timeout or TCP reset from the stalled client. To clean up the stream, the web server invokes ActionController::Live::Buffer#abort. The Buffer#abort method is designed to set @aborted = true and flush the queue via @buf.clear, which would normally unblock any waiting producer.

However, Buffer#abort also wraps its logic inside a synchronize do ... end block tied to the same MonitorMixin instance. When the web server thread calls Buffer#abort, it attempts to acquire the monitor lock already held by the producer thread sleeping inside Buffer#close. The producer thread cannot release the monitor lock until @buf is drained or cleared, and the consumer thread cannot clear @buf until it acquires the monitor lock. This circular wait condition fulfills all requirements for a classic resource deadlock.

Code Analysis

The vulnerable implementation resides in actionpack/lib/action_controller/metal/live.rb within the ActionController::Live::Buffer class. Prior to the patch, close and abort both relied on the monitor lock while close performed a potentially blocking queue insertion:

# Vulnerable implementation in actionpack/lib/action_controller/metal/live.rb
def close
  synchronize do
    super
    @buf.push nil     # Blocks indefinitely if @buf (SizedQueue) is full
    @cv.broadcast
  end
end
 
def abort
  synchronize do     # Blocks waiting for the monitor lock held by #close
    @aborted = true
    @buf.clear
  end
end

Rails maintainer Jeremy Daer resolved the vulnerability in commits ccbfc09690fef05fdd2a9969c917a71b92cc98fc (for Rails 8.1.4.1) and 27ad18ed5900dee28ba20e9c6fc718ff751fa215 (for Rails 8.0.5.2). The patch eliminates the blocking sentinel push in Buffer#close and replaces it with Ruby's native Thread::Queue#close (@buf.close).

File: actionpack/lib/action_controller/metal/live.rb
@@ -218,7 +218,11 @@ def write(string)
           @response.delete_header "Content-Length"
         end
 
-        super
+        begin
+          super
+        rescue ClosedQueueError
+          raise IOError, "closed stream" if connected?
+        end
 
         unless connected?
           @buf.clear
@@ -240,14 +244,14 @@ def writeln(string)
       def close
         synchronize do
           super
-          @buf.push nil
+          @buf.close      # Non-blocking queue closure replaces blocking sentinel push
           @cv.broadcast
         end
       end
@@ -255,7 +259,9 @@ def close
       def abort
         synchronize do
           @aborted = true
+          @buf.close      # Immediately closes queue and wakes blocked writers/readers
           @buf.clear
+          @cv.broadcast
         end
       end

Calling @buf.close on a Thread::SizedQueue transitions the queue state to closed immediately without inserting an element and without waiting for free capacity. Consequently, Buffer#close executes non-blockingly and releases the synchronize monitor lock immediately. Additionally, when @buf.close is called inside Buffer#abort, any producer thread currently blocked inside @buf.push (via Buffer#write) immediately wakes up with a ClosedQueueError exception.

To handle this exception cleanly, the patch wraps the super call inside Buffer#write with a begin ... rescue ClosedQueueError block. If the queue was closed while the client connection was still considered active, it raises IOError, "closed stream"; if the client disconnected (!connected?), it falls through to clear the buffer and raise ActionController::Live::ClientDisconnected. The patch is complete and directly eliminates the circular wait condition across all producer, consumer, and abort code paths.

Exploitation

Exploiting CVE-2026-126072 requires no authentication if the target application exposes a public streaming endpoint or utilizes Active Storage in proxy mode (ActiveStorage::Blobs::ProxyController). In Active Storage proxy mode—configured via config.active_storage.resolve_model_to_route = :rails_storage_proxy or accessed via rails_storage_proxy_path—Rails streams file blobs from backend storage services through the Rails process using send_blob_stream, which is built on ActionController::Live.

To trigger the deadlock on a target endpoint, an attacker performs the following steps:

  1. Initiate Stream: Open a raw TCP/TLS connection to the Rails server and send an HTTP GET request to a streaming route (e.g., /rails/active_storage/blobs/proxy/... for a file larger than the stream buffer capacity).
  2. Induce Backpressure: Read the initial HTTP response headers, then stop reading from the socket or advertise a TCP Receive Window (RCV.WND) of 0. This stalls socket writes on the server once the OS socket send buffer fills.
  3. Saturate SizedQueue: While the consumer thread is stalled on the socket, the Rails producer thread continues generating chunks until @buf (Thread::SizedQueue) reaches its slot limit and the producer completes the stream payload, entering Buffer#close.
  4. Trigger Abort: Wait for the web server's socket write timeout to expire, or abruptly terminate the connection via a TCP RST packet once the producer thread has entered Buffer#close. The web server invokes Buffer#abort, deadlocking both threads.

> [!NOTE] > For the deadlock to occur, the total response payload generated by the controller must be small enough to finish while filling the kernel socket buffer and the SizedQueue, or the producer must enter Buffer#close precisely while @buf is full. If the payload is infinitely large, the producer blocks inside Buffer#write (which does not hold the synchronize lock across @buf.push) rather than Buffer#close.

Because a default Puma deployment typically configures 5 to 16 threads per worker process, an attacker only needs to establish a small number of concurrent connections (e.g., 16 to 64 requests) to permanently lock every worker thread across all Puma cluster processes. Unlike standard slowloris attacks where connections release once the client disconnects or times out, threads deadlocked via CVE-2026-126072 remain hung permanently even after the attacker closes their sockets, requiring a server process restart to recover.

Impact Assessment

The primary impact of CVE-2026-126072 is a persistent Denial of Service affecting application availability. The vulnerability is rated 5.9 (Medium) under CVSS v3.1 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H). The High Attack Complexity (AC:H) metric reflects the requirement that the response stream must fill the bounded SizedQueue and call Buffer#close prior to Buffer#abort being invoked, whereas Confidentiality (C:N) and Integrity (I:N) remain unaffected.

When successfully triggered, each exploit cycle permanently removes one web server worker thread and one ActionController::Live thread from service. Because Ruby web servers such as Puma rely on fixed-size thread pools to process all incoming HTTP requests, exhausting these threads prevents the server from answering any further requests—including static routes or non-streaming controller actions.

Furthermore, because the threads are blocked on an internal Ruby MonitorMixin mutex rather than an I/O select loop, standard application-level request timeout gems or socket timeouts cannot free the threads. Unless an external process supervisor or Puma's master process watchdog detects that the entire worker process has stopped responding and terminates it via SIGKILL, the application remains unresponsive indefinitely.

As of November 2026, CVE-2026-126072 has an EPSS score of 0.00411 (35.91st percentile) and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. No weaponized public exploit tooling has been observed in the wild, though the concurrency test cases included in the official Rails patch clearly demonstrate the mechanics required to reproduce the hang.

Remediation

The definitive remediation is to upgrade Ruby on Rails (rails and actionpack gems) to a patched release that replaces blocking sentinel pushes with non-blocking queue closures (@buf.close).

  • Rails 8.1 branch: Upgrade to 8.1.4.1 or later.
  • Rails 8.0 branch: Upgrade to 8.0.5.2 or later.

Update your application's Gemfile and execute Bundler to apply the patched version:

# Update Gemfile constraint to >= 8.1.4.1 or >= 8.0.5.2, then run:
bundle update rails actionpack

For environments where an immediate Rails upgrade cannot be deployed, operators can implement several compensating controls to reduce exposure:

  1. Disable Active Storage Proxy Mode: If your application uses Active Storage proxy mode for public files, reconfigure it to use redirect mode (:rails_storage_redirect). Redirect mode offloads file streaming directly to cloud object storage (such as Amazon S3 or Google Cloud Storage) via signed URLs, bypassing ActionController::Live entirely:
# config/environments/production.rb
config.active_storage.resolve_model_to_route = :rails_storage_redirect
  1. Enable Reverse Proxy Response Buffering: Deploy a reverse proxy such as NGINX in front of the Rails application server with proxy_buffering on; enabled. When proxy buffering is active, NGINX reads the entire streamed response from Puma at local loopback/LAN speeds into memory or disk buffers before transmitting it to the remote client. This isolates the Rails SizedQueue from client-side TCP window manipulation, provided the streamed response fits within NGINX's configured proxy_max_temp_file_size.

  2. Configure Process-Level Worker Watchdogs: In clustered Puma deployments, ensure worker_timeout is configured so that the cluster master process forcibly terminates (SIGKILL) and respawns worker processes whose threads become completely unresponsive.

Official Patches

Ruby on RailsOfficial GitHub Security Advisory (GHSA-rwvh-fw6r-pwvp)
Ruby on RailsRails v8.1.4.1 Release Notes
Ruby on RailsRails v8.0.5.2 Release Notes

Fix Analysis (2)

Technical Appendix

CVSS Score
5.9/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.41%
Top 64% most exploited

Affected Systems

Ruby on Rails (rails)Action Pack (actionpack)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Action Pack / Ruby on Rails
Ruby on Rails
< 8.0.5.28.0.5.2
Action Pack / Ruby on Rails
Ruby on Rails
>= 8.1, < 8.1.4.18.1.4.1
AttributeDetail
Vulnerability TypeCWE-833: Deadlock
Attack VectorNetwork (HTTP/HTTPS Streaming)
CVSS v3.1 Score5.9 Medium (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H)
EPSS Score0.41% (35.91st Percentile)
ImpactPermanent Worker Thread Exhaustion / Denial of Service
Affected ComponentActionController::Live::Buffer (actionpack)
Patched VersionsRails 8.1.4.1, Rails 8.0.5.2
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
T1499.003Endpoint Denial of Service: Application Exhaustion Flood
Impact
CWE-833
Deadlock

The software contains two or more concurrent execution paths that compete for the same shared resources, and each execution path waits for another execution path to release a resource, causing all execution paths to stop making progress.

Vulnerability Timeline

Maintainer Jeremy Daer commits the fix to Rails (commits 27ad18ed5900dee28ba20e9c6fc718ff751fa215 and ccbfc09690fef05fdd2a9969c917a71b92cc98fc).
2026-10-24
Rails release preparation commit 3f290b759c8e2ffe7c257951b1255740751355e4 sets version to 8.1.4.1.
2026-11-03
GitHub Security Advisory GHSA-rwvh-fw6r-pwvp and CVE-2026-126072 officially published.
2026-11-10
NVD completes metadata analysis and assigns CVSS v3.1 score of 5.9.
2026-11-12

References & Sources

  • [1]GitHub Security Advisory: GHSA-rwvh-fw6r-pwvp
  • [2]NVD CVE-2026-126072 Record
  • [3]CVE.org Record: CVE-2026-126072
  • [4]Fix Commit for Rails v8.1.4.1
  • [5]Fix Commit for Rails v8.0.5.2
  • [6]Ruby on Rails Release v8.1.4.1
  • [7]Ruby on Rails Release v8.0.5.2

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 2 hours ago•CVE-2026-126192
6.3

CVE-2026-126192: Quadratic Algorithmic Complexity Denial of Service in Soup Sieve CSS Selector Engine

CVE-2026-126192 is an algorithmic complexity vulnerability in Soup Sieve (soupsieve), the default CSS selector engine for Beautiful Soup 4 in Python. Evaluating a CSS selector that contains a general sibling combinator (~) nested inside a :has() relational pseudo-class against attacker-controlled HTML markup triggers O(n^2) quadratic evaluation complexity when the right-hand sibling target is absent. An unauthenticated attacker can supply small HTML documents containing thousands of sibling elements to exhaust CPU resources and stall application worker threads.

Alon Barad
Alon Barad
2 views•10 min read
•about 3 hours ago•CVE-2026-126190
7.5

CVE-2026-126190: Polynomial-Time Denial of Service in Soup Sieve CSS Descendant Combinator

CVE-2026-126190 (GHSA-mmhr-3c5m-f64f) is an inefficient algorithmic complexity vulnerability (CWE-407) in Soup Sieve, the default CSS selector library integrated into Beautiful Soup 4. In versions prior to 4.0, evaluating CSS selectors that contain descendant combinators (whitespace between compound selectors) executes an unmemoized upward traversal of the document tree. When evaluated against deeply nested HTML or XML documents, the time complexity scales polynomially at O(n^(k+1)), where n is the tree depth and k is the number of descendant combinators. Remote attackers can supply compact, deeply nested markup (1 KB to 4 KB) to trigger prolonged CPU starvation and denial of service in Python applications.

Amit Schendel
Amit Schendel
3 views•9 min read
•about 4 hours ago•CVE-2026-122006
9.8

CVE-2026-122006: Unauthenticated Arbitrary File Read and Write in Ruby on Rails Active Storage via ImageMagick

CVE-2026-122006 is a critical vulnerability in the Ruby on Rails Active Storage framework affecting versions prior to 8.0.5.2 and 8.1.4.1. When an application processes uploaded file attachments using the ImageMagick variant processor (:mini_magick), the framework invokes ImageMagick's metadata analyzer without applying a restrictive security policy. An unauthenticated attacker can upload a crafted file containing directives for unrestricted ImageMagick coders, such as Magick Scripting Language (MSL), resulting in arbitrary server-side file read and write operations. In deployments using the Disk storage service, attackers can exfiltrate sensitive application configuration files, including secret_key_base and master encryption keys, which enables remote code execution via signed payload forgery.

Alon Barad
Alon Barad
3 views•10 min read
•about 5 hours ago•CVE-2026-128065
6.9

CVE-2026-128065: Authorization Bypass via Unapplied RBAC Overrides in @grpc/grpc-js-xds

CVE-2026-128065 (also tracked as GHSA-gg5j-6742-v827) is an authorization bypass vulnerability affecting the @grpc/grpc-js-xds library in the Node.js gRPC ecosystem. In versions 1.14.0 and 1.14.1, XdsServer acknowledges Route and VirtualHost resources from an xDS control plane while failing to parse, store, or apply RBACPerRoute override configurations. When experimental RBAC is enabled and a broad top-level ALLOW policy is paired with restrictive VirtualHost or Route overrides, the server evaluates requests strictly against the top-level policy, permitting unauthorized RPC execution.

Alon Barad
Alon Barad
4 views•10 min read
•about 6 hours ago•CVE-2026-126188
3.7

CVE-2026-126188: Local Media Information Disclosure in Ruby on Rails Active Storage via FFmpeg Demuxer Selection

CVE-2026-126188 (GHSA-3f4q-8x5m-76v8) is an information disclosure vulnerability in the Active Storage component of Ruby on Rails prior to versions 8.0.5.2 and 8.1.4.1. When generating previews or extracting metadata from uploaded media files, Active Storage invokes the external command-line tools ffmpeg and ffprobe without specifying a format whitelist. An attacker who can upload a crafted HTTP Live Streaming (HLS) playlist while declaring a standard video or audio MIME type can cause FFmpeg to engage the HLS demuxer, resolve file:// segment URIs, and disclose visual frames or metadata from local media files readable by the Rails application process.

Amit Schendel
Amit Schendel
3 views•10 min read
•about 7 hours ago•CVE-2026-126206
5.3

CVE-2026-126206: Cross-Site Scripting via HTML5 Named Solidus Entity in Loofah URI Validator

CVE-2026-126206 (GHSA-jcff-cj73-4w4f) is a Cross-Site Scripting (XSS) vulnerability in version 2.25.2 of the Loofah Ruby gem. The vulnerability stems from an incomplete character reference normalization routine in the `Loofah::HTML5::Scrub.allowed_uri?` method, which fails to decode the HTML5 named character reference `&sol;` (representing the solidus character `/`). Consequently, a crafted `data:` URI such as `data:text&sol;html,<script>alert(1)</script>` bypasses mediatype validation by triggering the WHATWG MIME parser fallback to `text/plain`, while web browsers later resolve `&sol;` to `/` and execute the payload as `text/html`.

Alon Barad
Alon Barad
3 views•9 min read