Skip to content

Commit

Permalink
Fix vulnerability: restrict bitstring modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
sabiwara committed Aug 13, 2023
1 parent 1890751 commit f8cda02
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Dev

### Bug fixes

- Fix vulnerability allowing an attacker to crash the VM using bitstrings

## v0.3.2 (2023-08-12)

### Enhancements
Expand Down
72 changes: 72 additions & 0 deletions lib/dune/parser/sanitizer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ defmodule Dune.Parser.Sanitizer do
message = "dune parsing error: failed to safe parse\n #{Macro.to_string(ast)}"
new_failure(:parsing, message, unsafe.atom_mapping)

{:bin_modifier_restricted, ast} ->
message =
"** (DuneRestrictedError) bitstring modifier is restricted:\n #{Macro.to_string(ast)}"

new_failure(:restricted, message, unsafe.atom_mapping)

{:bin_modifier_size, max_size} ->
message = "** (DuneRestrictedError) size modifiers above #{max_size} are restricted"
new_failure(:restricted, message, unsafe.atom_mapping)

{:exception, error} ->
message = Exception.format(:error, error)
new_failure(:exception, message, unsafe.atom_mapping)
Expand Down Expand Up @@ -433,6 +443,19 @@ defmodule Dune.Parser.Sanitizer do
sanitize_capture(ast, env)
end

defp do_sanitize({:<<>>, meta, args}, env) do
sanitized_args =
Enum.map(args, fn
{:"::", meta, [expr, modifier]} ->
{:"::", meta, [do_sanitize(expr, env), check_bin_modifier(modifier)]}

arg ->
do_sanitize(arg, env)
end)

{:<<>>, meta, sanitized_args}
end

defp do_sanitize({{:., _, [left, right]}, ctx, args} = raw, env)
when is_atom(right) and is_list(args) do
case left do
Expand Down Expand Up @@ -696,6 +719,55 @@ defmodule Dune.Parser.Sanitizer do
put_elem(raw, 2, safe_args)
end

@max_segment_size 256
@binary_modifiers [:binary, :bytes]
@allowed_modifiers [:integer, :float, :bits, :bitstring, :utf8, :utf16, :utf32] ++
[:signed, :unsigned, :little, :big, :native]

defp check_bin_modifier(modifier) do
{size, unit} = check_bin_modifier_size(modifier, 8, nil)
unit = unit || 1

if size * unit > @max_segment_size do
throw({:bin_modifier_size, @max_segment_size})
end

modifier
end

defp check_bin_modifier_size({:-, _, [left, right]}, size, unit) do
{size, unit} = check_bin_modifier_size(left, size, unit)
check_bin_modifier_size(right, size, unit)
end

defp check_bin_modifier_size(modifier, size, unit) do
case modifier do
new_size when is_integer(new_size) ->
{new_size, unit}

{:size, _, [new_size]} when is_integer(new_size) ->
{new_size, unit}

{:unit, _, [new_unit]} when is_integer(new_unit) ->
{size, new_unit}

{:*, _, [new_size, new_unit]} when is_integer(new_size) and is_integer(new_unit) ->
{new_size, new_unit}

{:size, _, [{:^, _, [{var, _, ctx}]}]} when is_atom(var) and is_atom(ctx) ->
{size, unit}

{atom, _, ctx} when atom in @binary_modifiers and is_atom(ctx) ->
{size, unit || 8}

{atom, _, ctx} when atom in @allowed_modifiers and is_atom(ctx) ->
{size, unit}

other ->
throw({:bin_modifier_restricted, other})
end
end

defp env_variable do
Macro.var(@env_variable_name, nil)
end
Expand Down
114 changes: 114 additions & 0 deletions test/dune_string_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,70 @@ defmodule DuneStringTest do
} = ~E'~w[#{String.downcase("FOO")} bar baz]a'
end

@tag :lts_only
test "bitstring modifiers" do
assert %Success{
value: <<3>>,
inspected: "<<3>>"
} = ~E'<<3>>'

assert %Success{
value: <<3::4>>,
inspected: "<<3::size(4)>>"
} = ~E'<<3::4>>'

assert %Success{
value: "ߧ",
inspected: ~S'"ߧ"'
} = ~E'<<2023::utf8>>'

assert %Success{
value: 12520,
inspected: "12520"
} = ~E'<<c::utf8>> = "ヨ"; c'

assert %Success{
value: <<3::4>>,
inspected: "<<3::size(4)>>"
} = ~E'<<3::size(4)>>'

assert %Success{
value: 6_382_179,
inspected: "6382179"
} = ~E'<<c::size(24)>> = "abc"; c'

assert %Success{
value: <<2, 3>>,
inspected: "<<2, 3>>"
} = ~E'<<_::binary-size(2), rest::binary>> = <<0, 1, 2, 3>>; rest'

assert %Success{
value: <<0, 0, 0, 1>>,
inspected: "<<0, 0, 0, 1>>"
} = ~E'''
x = 1
<<x::8*4>>
'''

assert %Success{
value: <<0, 0, 0, 1>>,
inspected: "<<0, 0, 0, 1>>"
} = ~E'''
x = 1
<<x::size(8)-unit(4)>>
'''

assert %Success{
value: {"Frank", "Walrus"},
inspected: ~S'{"Frank", "Walrus"}'
} = ~E'''
name_size = 5
<<name::binary-size(^name_size), " the ", species::binary>> = <<"Frank the Walrus">>
{name, species}
{"Frank", "Walrus"}
'''
end

test "binary comprehensions" do
assert %Success{
value: [{213, 45, 132}, {64, 76, 32}],
Expand Down Expand Up @@ -672,6 +736,56 @@ defmodule DuneStringTest do
message: "** (DuneRestrictedError) function __ENV__/0 is restricted"
} = ~E'__ENV__.requires'
end

test "bitstring modifiers" do
assert %Failure{
type: :restricted,
message: "** (DuneRestrictedError) size modifiers above 256 are restricted"
} = ~E'<<0::123456789123456789>>'

assert %Failure{
type: :restricted,
message: "** (DuneRestrictedError) size modifiers above 256 are restricted"
} = ~E'<<0::size(257)>>'

assert %Failure{
type: :restricted,
message: "** (DuneRestrictedError) size modifiers above 256 are restricted"
} = ~E'<<0::256*2>>'

assert %Failure{
type: :restricted,
message: "** (DuneRestrictedError) size modifiers above 256 are restricted"
} = ~E'<<0::integer-size(257)>>'

assert %Failure{
type: :restricted,
message: "** (DuneRestrictedError) size modifiers above 256 are restricted"
} = ~E'<<0::integer-size(256)-unit(2)>>'

assert %Failure{
type: :restricted,
message:
"** (DuneRestrictedError) bitstring modifier is restricted:\n size(x)"
} = ~E'''
x = 123456789123456789
<<0::size(x)>>
'''

assert %Failure{
message:
"** (DuneRestrictedError) bitstring modifier is restricted:\n unquote(1)"
} = ~E'<<1::unquote(1)>>'

assert %Failure{
message:
"** (DuneRestrictedError) bitstring modifier is restricted:\n unquote(1)"
} = ~E'<<1::integer-unquote(1)>>'

assert %Failure{
message: "** (DuneRestrictedError) function unquote/1 is restricted"
} = ~E'<<unquote(1)>>'
end
end

describe "process restrictions" do
Expand Down

0 comments on commit f8cda02

Please sign in to comment.