Skip to content

Commit 3549775

Browse files
committed
Use non_executable_binary_to_term on loopback pubsub
1 parent c3f7e34 commit 3549775

4 files changed

Lines changed: 92 additions & 6 deletions

File tree

lib/mix/lib/mix/sync/pubsub.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ defmodule Mix.Sync.PubSub do
263263

264264
defp decode_data(binary) do
265265
try do
266-
{:ok, :erlang.binary_to_term(binary)}
266+
{:ok, Mix.Utils.non_executable_binary_to_term(binary)}
267267
rescue
268268
_error -> :error
269269
end

lib/mix/lib/mix/tasks/format.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ defmodule Mix.Tasks.Format do
499499

500500
defp read_manifest(manifest) do
501501
with {:ok, binary} <- File.read(manifest),
502-
{:ok, {@manifest_vsn, entry, sources}} <- safe_binary_to_term(binary),
502+
{:ok, {@manifest_vsn, entry, sources}} <- non_raising_binary_to_term(binary),
503503
expanded_sources = Enum.flat_map(sources, &Path.wildcard(&1, match_dot: true)),
504504
false <- Mix.Utils.stale?([Mix.Project.config_mtime() | expanded_sources], [manifest]) do
505505
{entry, sources}
@@ -508,7 +508,7 @@ defmodule Mix.Tasks.Format do
508508
end
509509
end
510510

511-
defp safe_binary_to_term(binary) do
511+
defp non_raising_binary_to_term(binary) do
512512
{:ok, :erlang.binary_to_term(binary)}
513513
rescue
514514
_ -> :error

lib/mix/lib/mix/utils.ex

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,76 @@ defmodule Mix.Utils do
1111
# For bootstrapping purposes
1212
@compile {:no_warn_undefined, Logger}
1313

14+
@doc """
15+
A restricted version of `:erlang.binary_to_term/2` that forbids
16+
*executable* terms, such as anonymous functions.
17+
18+
The `opts` are given to the underlying `:erlang.binary_to_term/2`
19+
call, with an empty list as a default.
20+
21+
By default this function does not restrict atoms, as an atom
22+
interned in one node may not yet have been interned on another
23+
(except for releases, which preload all code).
24+
25+
If you want to avoid atoms from being created, then you can pass
26+
`[:safe]` as options, as that will also enable the safety mechanisms
27+
from `:erlang.binary_to_term/2` itself.
28+
"""
29+
@spec non_executable_binary_to_term(binary(), [atom()]) :: term()
30+
def non_executable_binary_to_term(binary, opts \\ []) when is_binary(binary) do
31+
term = :erlang.binary_to_term(binary, opts)
32+
non_executable_terms(term)
33+
term
34+
end
35+
36+
defp non_executable_terms(list) when is_list(list) do
37+
non_executable_list(list)
38+
end
39+
40+
defp non_executable_terms(tuple) when is_tuple(tuple) do
41+
non_executable_tuple(tuple, tuple_size(tuple))
42+
end
43+
44+
defp non_executable_terms(map) when is_map(map) do
45+
folder = fn key, value, acc ->
46+
non_executable_terms(key)
47+
non_executable_terms(value)
48+
acc
49+
end
50+
51+
:maps.fold(folder, map, map)
52+
end
53+
54+
defp non_executable_terms(other)
55+
when is_atom(other) or is_number(other) or is_bitstring(other) or is_pid(other) or
56+
is_reference(other) do
57+
other
58+
end
59+
60+
defp non_executable_terms(other) do
61+
raise ArgumentError,
62+
"cannot deserialize #{inspect(other)}, the term is not safe for deserialization"
63+
end
64+
65+
defp non_executable_list([]), do: :ok
66+
67+
defp non_executable_list([h | t]) when is_list(t) do
68+
non_executable_terms(h)
69+
non_executable_list(t)
70+
end
71+
72+
defp non_executable_list([h | t]) do
73+
non_executable_terms(h)
74+
non_executable_terms(t)
75+
end
76+
77+
defp non_executable_tuple(_tuple, 0), do: :ok
78+
79+
defp non_executable_tuple(tuple, n) do
80+
non_executable_terms(:erlang.element(n, tuple))
81+
non_executable_tuple(tuple, n - 1)
82+
end
83+
1484
@doc """
1585
Returns the apps of a project with no filtering.
1686

lib/mix/test/mix/utils_test.exs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ defmodule Mix.UtilsTest do
2828
end)
2929
end
3030

31+
test "non_executable_binary_to_term" do
32+
value = %{1 => {:foo, ["bar", 2.0, %URI{}, [self() | make_ref()], <<0::4>>]}}
33+
assert Mix.Utils.non_executable_binary_to_term(:erlang.term_to_binary(value)) == value
34+
35+
assert_raise ArgumentError, fn ->
36+
Mix.Utils.non_executable_binary_to_term(
37+
:erlang.term_to_binary(%{1 => {:foo, [fn -> :bar end]}})
38+
)
39+
end
40+
41+
assert_raise ArgumentError, fn ->
42+
Mix.Utils.non_executable_binary_to_term(
43+
<<131, 100, 0, 7, 103, 114, 105, 102, 102, 105, 110>>,
44+
[:safe]
45+
)
46+
end
47+
end
48+
3149
test "command to module" do
3250
assert Mix.Utils.command_to_module("cheers", Mix.Tasks) == {:module, Mix.Tasks.Cheers}
3351
assert Mix.Utils.command_to_module("unknown", Mix.Tasks) == {:error, :nofile}
@@ -241,9 +259,7 @@ defmodule Mix.UtilsTest do
241259

242260
assert File.read!("graph.dot") == """
243261
digraph "graph" {
244-
"foo
245-
bar\r
246-
baz"
262+
"foo \nbar\r\nbaz"
247263
}
248264
"""
249265
end)

0 commit comments

Comments
 (0)