|
| 1 | +%% Vendored from hex_core MANUALLY!, do not edit manually |
| 2 | + |
| 3 | +%% @hidden |
| 4 | +%% Safe deserialization of Erlang terms from binary. |
| 5 | +%% |
| 6 | +%% This module provides a restricted version of `binary_to_term/1' that: |
| 7 | +%% - Uses the `safe' option to prevent creation of new atoms (DoS protection) |
| 8 | +%% - Validates that the term contains no executable code (RCE protection) |
| 9 | +%% |
| 10 | +%% Inspired by Plug.Crypto's non_executable_binary_to_term: |
| 11 | +%% https://github.com/elixir-plug/plug_crypto/blob/c326c3c743b18cf5f4b12735d06dd90c72dcd779/lib/plug/crypto.ex |
| 12 | +-module(mix_hex_safe_binary_to_term). |
| 13 | + |
| 14 | +-export([safe_binary_to_term/1]). |
| 15 | + |
| 16 | +-type unsafe_term() :: function() | port(). |
| 17 | +-type error_reason() :: invalid_term | {unsafe_term, unsafe_term()}. |
| 18 | + |
| 19 | +-spec safe_binary_to_term(binary()) -> {ok, term()} | {error, error_reason()}. |
| 20 | +safe_binary_to_term(Binary) when is_binary(Binary) -> |
| 21 | + try binary_to_term(Binary, [safe]) of |
| 22 | + Term -> |
| 23 | + case validate_term(Term) of |
| 24 | + ok -> {ok, Term}; |
| 25 | + {error, _} = Error -> Error |
| 26 | + end |
| 27 | + catch |
| 28 | + error:badarg -> |
| 29 | + {error, invalid_term} |
| 30 | + end. |
| 31 | + |
| 32 | +-spec validate_term(term()) -> ok | {error, {unsafe_term, term()}}. |
| 33 | +validate_term(Term) when is_list(Term) -> |
| 34 | + validate_list(Term); |
| 35 | +validate_term(Term) when is_tuple(Term) -> |
| 36 | + validate_tuple(Term, tuple_size(Term)); |
| 37 | +validate_term(Term) when is_map(Term) -> |
| 38 | + validate_map(Term); |
| 39 | +validate_term(Term) when |
| 40 | + is_atom(Term); |
| 41 | + is_number(Term); |
| 42 | + is_bitstring(Term); |
| 43 | + is_pid(Term); |
| 44 | + is_reference(Term) |
| 45 | +-> |
| 46 | + ok; |
| 47 | +validate_term(Term) -> |
| 48 | + {error, {unsafe_term, Term}}. |
| 49 | + |
| 50 | +-spec validate_list(list()) -> ok | {error, {unsafe_term, term()}}. |
| 51 | +validate_list([]) -> |
| 52 | + ok; |
| 53 | +validate_list([H | T]) when is_list(T) -> |
| 54 | + case validate_term(H) of |
| 55 | + ok -> validate_list(T); |
| 56 | + Error -> Error |
| 57 | + end; |
| 58 | +validate_list([H | T]) -> |
| 59 | + %% Improper list |
| 60 | + case validate_term(H) of |
| 61 | + ok -> validate_term(T); |
| 62 | + Error -> Error |
| 63 | + end. |
| 64 | + |
| 65 | +-spec validate_tuple(tuple(), non_neg_integer()) -> ok | {error, {unsafe_term, term()}}. |
| 66 | +validate_tuple(_Tuple, 0) -> |
| 67 | + ok; |
| 68 | +validate_tuple(Tuple, N) -> |
| 69 | + case validate_term(element(N, Tuple)) of |
| 70 | + ok -> validate_tuple(Tuple, N - 1); |
| 71 | + Error -> Error |
| 72 | + end. |
| 73 | + |
| 74 | +-spec validate_map(map()) -> ok | {error, {unsafe_term, term()}}. |
| 75 | +validate_map(Map) -> |
| 76 | + try |
| 77 | + maps:fold( |
| 78 | + fun(Key, Value, ok) -> |
| 79 | + case validate_term(Key) of |
| 80 | + ok -> |
| 81 | + case validate_term(Value) of |
| 82 | + ok -> ok; |
| 83 | + Error -> throw(Error) |
| 84 | + end; |
| 85 | + Error -> |
| 86 | + throw(Error) |
| 87 | + end |
| 88 | + end, |
| 89 | + ok, |
| 90 | + Map |
| 91 | + ) |
| 92 | + catch |
| 93 | + throw:{error, _} = Error -> Error |
| 94 | + end. |
0 commit comments