Skip to content

Commit 64e6707

Browse files
committed
Limit version numbers to 14 bytes
This avoids parsing too large integers. CVE-2026-49762 GHSA-w2h8-8x3g-278p
1 parent 657a7b3 commit 64e6707

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

lib/elixir/lib/version.ex

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ defmodule Version do
1818
1919
MAJOR.MINOR.PATCH
2020
21+
Each numeric component is limited to at most 14 digits.
22+
2123
Pre-releases are supported by optionally appending a hyphen and a series of
2224
period-separated identifiers immediately following the patch version.
2325
Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
2426
2527
"1.0.0-alpha.3"
2628
29+
Numeric pre-release identifiers are also limited to at most 14 digits.
30+
2731
Build information can be added by appending a plus sign and a series of
2832
dot-separated identifiers immediately following the patch or pre-release version.
2933
Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
@@ -520,6 +524,8 @@ defmodule Version do
520524
defmodule Parser do
521525
@moduledoc false
522526

527+
@max_numeric_component_digits 14
528+
523529
operators = [
524530
{">=", :>=},
525531
{"<=", :<=},
@@ -621,7 +627,9 @@ defmodule Version do
621627
defp require_digits(nil), do: :error
622628

623629
defp require_digits(string) do
624-
if leading_zero?(string), do: :error, else: parse_digits(string, "")
630+
if leading_zero?(string) or byte_size(string) > @max_numeric_component_digits,
631+
do: :error,
632+
else: parse_digits(string, "")
625633
end
626634

627635
defp leading_zero?(<<?0, _, _::binary>>), do: true
@@ -649,6 +657,11 @@ defmodule Version do
649657
end
650658
end
651659

660+
defp convert_parts_to_integer([part | rest], acc)
661+
when byte_size(part) > @max_numeric_component_digits do
662+
if all_digits?(part), do: :error, else: convert_parts_to_integer(rest, [part | acc])
663+
end
664+
652665
defp convert_parts_to_integer([part | rest], acc) do
653666
case parse_digits(part, "") do
654667
{:ok, integer} ->
@@ -667,6 +680,10 @@ defmodule Version do
667680
{:ok, Enum.reverse(acc)}
668681
end
669682

683+
defp all_digits?(<<char, rest::binary>>) when char in ?0..?9, do: all_digits?(rest)
684+
defp all_digits?(<<>>), do: true
685+
defp all_digits?(_other), do: false
686+
670687
defp valid_identifier?(<<char, rest::binary>>)
671688
when char in ?0..?9
672689
when char in ?a..?z

lib/elixir/test/elixir/version_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,15 @@ defmodule VersionTest do
8484
assert {:ok, %Version{major: 1, minor: 4, patch: 5, pre: [6, 7, "eight"]}} =
8585
Version.parse("1.4.5-6.7.eight")
8686

87+
assert {:ok, %Version{major: 99_999_999_999_999, minor: 0, patch: 0}} =
88+
Version.parse("99999999999999.0.0")
89+
8790
assert {:ok, %Version{major: 1, minor: 4, patch: 5, pre: ["6-g3318bd5"]}} =
8891
Version.parse("1.4.5-6-g3318bd5+ignore")
8992

93+
assert {:ok, %Version{major: 1, minor: 0, patch: 0, pre: ["100000000000000-alpha"]}} =
94+
Version.parse("1.0.0-100000000000000-alpha")
95+
9096
assert Version.parse("foobar") == :error
9197
assert Version.parse("2") == :error
9298
assert Version.parse("2.") == :error
@@ -105,6 +111,13 @@ defmodule VersionTest do
105111
assert Version.parse("02.3.0") == :error
106112
assert Version.parse("0. 0.0") == :error
107113
assert Version.parse("0.1.0-&&pre") == :error
114+
assert Version.parse("100000000000000.0.0") == :error
115+
assert Version.parse("1.100000000000000.0") == :error
116+
assert Version.parse("1.0.100000000000000") == :error
117+
assert Version.parse("1.0.0-100000000000000") == :error
118+
119+
assert Version.parse("1.0.0+100000000000000") ==
120+
{:ok, %Version{major: 1, minor: 0, patch: 0, build: "100000000000000"}}
108121
end
109122

110123
test "to_string/1" do
@@ -338,6 +351,7 @@ defmodule VersionTest do
338351
assert Version.parse_requirement("1.2.3 and or 4.5.6") == :error
339352
assert Version.parse_requirement(">= 1") == :error
340353
assert Version.parse_requirement("1.2.3 >=") == :error
354+
assert Version.parse_requirement("100000000000000.0.0") == :error
341355
end
342356

343357
test "inspect/1" do

0 commit comments

Comments
 (0)