GHSA-327h-97cv-59xg: Unbounded port parsing in URI.parse/1 enables CPU exhaustion denial of service in Elixir

Published Oct 30, 2026
·
Updated

Summary

Improper Validation of Specified Quantity in Input vulnerability in the Elixir standard library's URI module allows an attacker who supplies a URL to cause a denial of service through CPU exhaustion.

URI.parse/1 converts the port component with String.tointeger/1 in splitauthority/1 (lib/elixir/lib/uri.ex) without bounding its length, so a port of up to 1,262,592 decimal digits produces a bignum of that size and a longer one raises SystemLimitError. Rendering the parsed URI back to text (URI.tostring/1, inspect/1, logging, error formatting) converts the bignum with :erlang.integertobinary/1, which is quadratic on Erlang/OTP 27, 28 and 29.0: a 400,000-digit port renders in about 2.6 seconds and a 1 MB port in about 18 seconds. Ports above 65535 are also accepted.

This issue affects elixir: from 0.5.0 before 1.18.6, from 1.19.0-rc.0 before 1.19.7, and from 1.20.0-rc.0 before 1.20.5.

Details

1. Unbounded conversion

splitauthority/1 matches the port with the regex (:(\d))? and converts it with String.tointeger/1. Nothing bounds the digit run, so the VM builds an integer of up to 1,262,592 decimal digits (about 30 ms to parse 400,000 digits) and raises SystemLimitError above that, which escapes URI.parse/1 instead of producing a %URI{}.

2. Render cost

URI.tostring/1 renders the port with Integer.tostring/1, and inspect/1 renders it the same way, both through :erlang.integertobinary/1. That conversion is quadratic on OTP 27, 28 and 29.0 and sub-quadratic from OTP 29.1 (https://github.com/erlang/otp/pull/11074). Per GHSA-8qw4-2chm-mvj2 it occupies a dirty CPU scheduler for its full duration.

3. Comparison with URI.new/1

Erlang/OTP limited the port to 5 bytes in :uristring.parse/1 as part of GHSA-8qw4-2chm-mvj2. URI.new/1 and URI.new!/1 use :uristring and get that limit on patched OTP versions. URI.parse/1 does its own parsing and does not.

4. Reach

Header-sourced URLs are bounded by HTTP server limits. Form and JSON body fields are not: Plug's default JSON body limit is 8 MB. One observed library path: a user-supplied URL passed to Req inside a Bandit request exits with the port in its arguments, and Bandit's error logging formats it, which took 2.8 seconds at 400,000 digits on OTP 28.

Proof of concept

1. Build url = "http://example.com:" <> String.duplicate("9", 400000). 2. URI.parse(url) returns in about 30 ms. 3. URI.tostring(URI.parse(url)) takes 2,613 ms on OTP 27.3.4.18, 2,604 ms on OTP 28.5 and 108 ms on OTP 29.1.1. With 200,000 digits it takes 654 ms on OTP 27 and 28, so the cost grows 4x per doubling. 4. The same digits placed in the path instead of the port parse and render in 1 ms or less. 5. URI.parse("http://example.com:65536").port returns 65536, and a port of 1,262,593 digits raises SystemLimitError.

Impact

An unauthenticated client that submits a URL with a port of hundreds of thousands of digits makes the server build a bignum of that size, and each later rendering of the parsed URI costs seconds of CPU on Erlang/OTP 27, 28 and 29.0. Sustained requests exhaust CPU on applications that parse and log, store or echo user-supplied URLs.

Workarounds

Reject long URLs before parsing them, for example anything above a few kilobytes, and reject a parsed port above 65535 before rendering or using the %URI{}. URI.new/1 is not affected on Erlang/OTP 27.3.4.17, 28.5.0.6, 29.0.6 and later, where :uristring limits the port to 5 digits.

Configurations

The application parses an attacker-supplied URL with URI.parse/1, for example a URL field in a form or JSON request body, and then renders the result or a value containing it. The multi-second render cost requires Erlang/OTP 27, 28 or 29.0; on OTP 29.1 and later rendering a 1 MB port takes under a second. URLs in HTTP headers and the request line are bounded by the server's size limits.

Other sources

Improper Validation of Specified Quantity in Input vulnerability in the Elixir standard library's URI module allows an attacker who supplies a URL to cause a denial of service through CPU exhaustion.

URI.parse/1 converts the port component with String.tointeger/1 in splitauthority/1 (lib/elixir/lib/uri.ex) without bounding its length, so a port of up to 1,262,592 decimal digits produces a bignum of that size and a longer one raises SystemLimitError. Rendering the parsed URI back to text (URI.tostring/1, inspect/1, logging, error formatting) converts the bignum with :erlang.integertobinary/1, which is quadratic on Erlang/OTP 27, 28 and 29.0: a 400,000-digit port renders in about 2.6 seconds and a 1 MB port in about 18 seconds. Ports above 65535 are also accepted.

This issue affects elixir: from 0.5.0 before 1.18.6, from 1.19.0-rc.0 before 1.19.7, and from 1.20.0-rc.0 before 1.20.5.

— OSV

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade elixir to a version that resolves this vulnerability.

    Fixed in 1.18.6
  2. Upgrade

    Upgrade elixir to a version that resolves this vulnerability.

    Fixed in 1.19.7
  3. Upgrade

    Upgrade elixir to a version that resolves this vulnerability.

    Fixed in 1.20.5
  4. Upgrade

    Upgrade Erlang/OTP to a version that resolves this vulnerability.

    Fixed in 29.1
  5. Compensating control

    Reject URLs above a few kilobytes before parsing, and reject any parsed port above 65535 before rendering or using the %URI{}.

  6. Compensating control

    Use URI.new/1 or URI.new!/1 instead of URI.parse/1 with Erlang/OTP 27.3.4.17, 28.5.0.6, or 29.0.6 and later, where the port is limited to 5 digits.

Event History

Oct 30, 2026
CVE Published
via OSV·08:54 AM
Data Sourced
via OSV·08:54 AM
DescriptionSeverityWeakness

Frequently Asked Questions

1

Which applications are realistically exposed to this issue?

Applications are exposed if they pass attacker-controlled URLs to URI.parse/1 and subsequently render, inspect, log, or include the parsed URI in error output. The greatest CPU impact occurs when a very large numeric port is converted back to text.

2

Does exploitation require authentication or user interaction?

No authentication or user interaction is required. An attacker needs only a path to supply a crafted URL containing an excessively long decimal port component.

3

Are ordinary out-of-range ports rejected?

No. URI.parse/1 accepts ports above 65535, in addition to accepting an unbounded number of decimal digits in the port component.

4

Which versions are affected?

Affected versions are Elixir 0.5.0 through before 1.18.6, 1.19.0-rc.0 through before 1.19.7, and 1.20.0-rc.0 through before 1.20.5.

5

Why can logging or error handling make the impact worse?

Rendering a parsed URI with URI.to_string/1, inspect/1, logging, or error formatting converts the large port integer back to text. On Erlang/OTP 27, 28, and 29.0, this conversion is quadratic; the reported examples take about 2.6 seconds for a 400,000-digit port and about 18 seconds for a 1 MB port.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203