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



GHSA-2X79-GWQ3-VXXM
8.7

GHSA-2x79-gwq3-vxxm: Infinite Loop Denial of Service in facil.io and iodine JSON Parser

Amit Schendel
Amit Schendel
Senior Security Researcher

Apr 15, 2026·6 min read·3 visits

PoC Available

Executive Summary (TL;DR)

A flaw in the JSON parser of facil.io and iodine causes an infinite loop when parsing malformed numerals starting with 'i' or 'I'. This allows unauthenticated remote attackers to exhaust CPU resources and cause a severe denial of service.

An uncontrolled resource consumption vulnerability in the facil.io C framework and the iodine Ruby gem allows remote attackers to cause a Denial of Service (DoS). The vulnerability is triggered by parsing crafted JSON payloads containing malformed numeral values, resulting in an infinite loop that exhausts CPU resources.

Vulnerability Overview

The vulnerability is located within the fio_json_parse function defined in the fio_json_parser.h header file. This parser functions as the core JSON processing unit for the facil.io C framework, which is designed for high-performance networking applications. The iodine Ruby gem heavily relies on this vendored parser for handling JSON operations in HTTP and WebSocket contexts.

The flaw represents a classic CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop'). When the JSON parser encounters specific malformed inputs, the internal state machine fails to advance the read cursor. The parser continuously attempts to evaluate the same byte sequence without making forward progress or transitioning to an error state.

Attackers exploit this behavior by submitting crafted JSON payloads containing nested structures combined with bare characters like i or I. Because the framework operates as an asynchronous event loop, triggering this infinite loop inside the single-threaded parsing process locks the executing core, rendering the process incapable of handling further requests.

Root Cause Analysis

The root cause originates in the numeral parsing logic implemented between lines 434 and 468 of lib/facil/fiobj/fio_json_parser.h. The parser utilizes an internal state machine to process JSON tokens sequentially. When the parser operates inside an array or object context and identifies a character starting a new value, it checks whether the sequence represents a numerical input.

Upon encountering the characters i or I—often used in systems to denote inf or Infinity—the parser state machine jumps to the numeral: label. At this stage, the parser invokes the fio_atol function, passing a double pointer (char **)&tmp to attempt standard integer conversion. The fio_atol function relies on standard numerical characters to consume the buffer.

When fio_atol processes a bare i character, it successfully consumes zero characters and returns immediately. Crucially, the pointer tmp remains equal to the original position pointer pos. Following this operation, the parser evaluates the condition if (!tmp || JSON_NUMERAL[*tmp]). The lookup table JSON_NUMERAL['i'] evaluates to false (0), bypassing the failure detection branch.

Because the failure branch is bypassed, the parser erroneously concludes that a valid numeric sequence was processed. It executes pos = tmp, updating the main parser position to the exact same memory location it started at. Since the parsing depth remains greater than zero and the end of the buffer has not been reached, the outer while loop restarts, immediately encountering the same i character and repeating the cycle infinitely.

Code Analysis

Analyzing the source code reveals the structural oversight in pointer validation. The vulnerable logic implicitly trusts that the underlying conversion functions (fio_atol and fio_atof) will either advance the memory pointer upon success or that the subsequent character lookup (JSON_NUMERAL) will fail decisively upon error.

The patch addresses this gap by explicitly verifying whether the pointer has moved. The developer introduced a direct comparison between the temporary pointer tmp and the position pointer pos.

--- a/lib/facil/fiobj/fio_json_parser.h
+++ b/lib/facil/fiobj/fio_json_parser.h
@@
        uint8_t *tmp = pos;
        long long i = fio_atol((char **)&tmp);
        if (tmp > limit)
          goto stop;
-       if (!tmp || JSON_NUMERAL[*tmp]) {
+       if (!tmp || tmp == pos || JSON_NUMERAL[*tmp]) {
          tmp = pos;
          double f = fio_atof((char **)&tmp);
          if (tmp > limit)
            goto stop;
-         if (!tmp || JSON_NUMERAL[*tmp])
+         if (!tmp || tmp == pos || JSON_NUMERAL[*tmp])
            goto error;
          fio_json_on_float(parser, f);
          pos = tmp;

By injecting the tmp == pos check, the condition enforces forward progress. If fio_atol or fio_atof consumes zero characters, tmp will equal pos. The logical OR condition will immediately trigger, causing the routine to recognize the invalid input and correctly jump to the error label, terminating the loop and gracefully failing the parse operation.

Exploitation Methodology

Exploitation requires no authentication, specific configurations, or complex interaction chains. An attacker merely needs network access to an endpoint that parses JSON payloads using facil.io or iodine. This typically involves sending an HTTP POST request with the Content-Type: application/json header and a specially crafted body.

The minimal payload required to trigger the vulnerability is simply [i. This string initiates an array and immediately introduces the malformed numeral character. Alternative payloads such as {"a":i or [""i achieve the same outcome by tricking the parser into evaluating the i character as a new value context.

The following Ruby code demonstrates a minimal proof-of-concept for an iodine server. When the Iodine::JSON.parse method is invoked on the request body containing [i, the worker thread immediately locks.

require "iodine"
 
APP = proc do |env|
  body = env["rack.input"].read.to_s
  warn "Parsing JSON: #{body.inspect}"
  Iodine::JSON.parse(body) # Triggers infinite loop on '[i'
  [200, { "Content-Type" => "text/plain" }, ["ok"]]
end
 
Iodine.listen service: :http, port: "3000", handler: APP
Iodine.threads = 1
Iodine.start

To execute the exploit against the vulnerable server, an attacker utilizes standard HTTP clients. The command printf '[i' | curl -X POST --data-binary @- http://127.0.0.1:3000/ dispatches the payload without URL encoding or trailing line breaks, guaranteeing the parser processes the exact bytes required to stall the state machine.

Impact Assessment

The operational impact of this vulnerability is a complete localized Denial of Service. When the infinite loop initiates, the underlying thread consumes 100% of the assigned CPU core's cycles. It will never timeout natively because the thread is actively processing code rather than blocking on I/O.

In architectures like facil.io and iodine which utilize asynchronous event loops, a blocked worker thread ceases processing all other queued connections assigned to that thread. If the application runs a single-threaded event loop, a single malformed request brings down the entire application.

In multi-threaded deployments, an attacker can achieve a total service outage by sending concurrent requests equal to the number of configured worker threads. Once all threads are trapped in the infinite loop parsing [i, the application becomes entirely unresponsive to legitimate traffic. The system administrator must forcefully terminate and restart the process to restore functionality.

Remediation Guidance

The primary remediation strategy requires updating the vulnerable packages to their patched versions. Applications relying on the iodine Ruby gem must upgrade to version greater than 0.7.58. Applications utilizing the facil.io C framework directly must integrate the latest commits from the master branch, specifically ensuring the fio_json_parser.h changes are applied.

For environments where immediate patching is impossible, network-level mitigations can provide temporary defense. Web Application Firewalls (WAFs) can be configured to inspect incoming JSON bodies for the specific byte sequences used in the exploit. Regular expressions targeting \[\s*i or :\s*i can intercept the most common variants of the payload before they reach the application layer.

Developers should verify their deployment architectures include adequate resource monitoring and process supervision. Configuring orchestration tools to health-check the application and restart uncooperative containers can reduce the total downtime during an active attack, though it does not eliminate the underlying vulnerability.

Official Patches

boazsegevOfficial Security Advisory and Patch Details

Technical Appendix

CVSS Score
8.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Affected Systems

facil.io C framework versions 0.7.5 and 0.7.6iodine Ruby gem versions <= 0.7.58

Affected Versions Detail

Product
Affected Versions
Fixed Version
facil.io
boazsegev
0.7.5, 0.7.6-
iodine
boazsegev
<= 0.7.58> 0.7.58
AttributeDetail
Vulnerability IDGHSA-2x79-gwq3-vxxm
CWECWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')
Attack VectorNetwork
CVSS v4.0 Score8.7 (High)
Exploit StatusProof of Concept Available
ImpactDenial of Service (CPU Exhaustion)

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
CWE-835
Loop with Unreachable Exit Condition ('Infinite Loop')

The program contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.

Known Exploits & Detection

Advisory PoCMinimal curl payload and Ruby testing server configuration.

Vulnerability Timeline

Advisory Published on GitHub
2026-04-14

References & Sources

  • [1]GitHub Security Advisory GHSA-2x79-gwq3-vxxm

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.