GitHub Security Advisory: GHSA-2x79-gwq3-vxxm
Package: iodine (RUBYGEMS)
Severity: HIGH (CVSS 0.0)
CWE: CWE-400: Uncontrolled Resource Consumption, CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')
Vulnerable versions: <= 0.7.58
Patched in: unpatched

Summary: Uncontrolled resource consumption and loop with unreachable exit condition in facil.io and downstream iodine ruby gem

Details:
### Summary
`fio_json_parse` can enter an infinite loop when it encounters a nested JSON value starting with `i` or `I`. The process spins in user space and pegs one CPU core at ~100% instead of returning a parse error. Because `iodine` vendors the same parser code, the issue also affects `iodine` when it parses attacker-controlled JSON.

The smallest reproducer found is `[i`. The quoted-value form that originally exposed the issue, `[""i`, reaches the same bug because the parser tolerates missing commas and then treats the trailing `i` as the start of another value.

### Details
The vulnerable logic is in `lib/facil/fiobj/fio_json_parser.h` around the numeral handling block (`0.7.5` / `0.7.6`: lines `434-468`; `master`: lines `434-468` in the current tree as tested).

This parser is reached from real library entry points, not just the header in isolation:

- `facil.io`: `lib/facil/fiobj/fiobj_json.c:377-387` (`fiobj_json2obj`) and `402-411` (`fiobj_hash_update_json`)
- `iodine`: `ext/iodine/iodine_json.c:161-177` (`iodine_json_convert`)
- `iodine`: `ext/iodine/fiobj_json.c:377-387` and `402-411`

Relevant flow:

1. Inside an array or object, the parser sees `i` or `I` and jumps to the `numeral:` label.
2. It calls `fio_atol((char **)&tmp)`.
3. For a bare `i` / `I`, `fio_atol` consumes zero characters and leaves `tmp == pos`.
4. The current code only falls back to float parsing when `JSON_NUMERAL[*tmp]` is true.
5. `JSON_NUMERAL['i'] == 0`, so the parser incorrectly accepts the value as an integer and sets `pos = tmp` without advancing.
6. Because parsing is still nested (`parser->depth > 0`), the outer loop continues forever with the same `pos`.

The same logic exists in `iodine`'s vendored copy at `ext/iodine/fio_json_parser.h` lines `434-468`.

Why the `[""i` form hangs:

1. The parser accepts the empty string `""` as the first array element.
2. It does not require a comma before the next token.
3. The trailing `i` is then parsed as a new nested value.
4. The zero-progress numeral path above causes the infinite loop.

Examples that trigger the bug:

- Array form, minimal: `[i`
- Object form: `{"a":i`
- After a quoted value in an array: `[""i`
- After a quoted value in an object: `{"a":""i`

## PoC
Environment used for verification:

- `facil.io` commit: `162df84001d66789efa883eebb0567426d00148e`
- `iodine` commit: `5bebba698d69023cf47829afe51052f8caa6c7f8`
- standalone compile against `fio_json_parser.h`

### Minimal standalone program

Use the normal HTTP stack. The following server calls `http_parse_body(h)`, which reaches `fiobj_json2obj` and then `fio_json_parse` for `Content-Type: application/json`.

```c
#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <time.h>
#include <fio.h>
#include <http.h>

static void on_request(http_s *h) {
  fprintf(stderr, "calling http_parse_body\n");
  fflush(stderr);
  http_parse_body(h);
  fprintf(stderr, "returned from http_parse_body\n");
  http_send_body(h, "ok\n", 3);
}

int main(void) {
  i