gavid
Can you break this "safe" interpreter?
I wanted to create a way to “safely” evaluate a string containing Elixir code. The code in the string should not be allowed to use any functions or modules (not even those from the Kernel e.g. defmodule/2).
Have I succeeded, or are there still ways to create a string that will allow the function to succeed while violating my criteria above?
def safe_eval(code) when is_binary(code) do
quoted_form = Code.string_to_quoted!(code, existing_atoms_only: true)
Macro.postwalk(quoted_form, fn node ->
if is_tuple(node) and (Kernel.elem(node, 0) in [:__aliases__]) do
raise "Cannot use aliases or references to other modules"
end
end)
{evaluated, _bindings} = Code.eval_string(code, [], %Macro.Env{})
{"ok", evaluated}
end
Most Liked
jhogberg
Try "<<0::size(123456789123456789)>>" ![]()
Why does it have to be Elixir code? If you can’t call any functions or modules then it sounds restricted enough that you could probably do what you want with a simple DSL instead, and writing an interpreter for a (say) small Lisp-like DSL doesn’t take very long.
adamu
Have you seen Dune - Sandbox for Elixir?
jhogberg
I guess it depends on the threat model, the underlying :erl_eval module wasn’t designed to be safe against a determined attacker: at the very least it’ll be possible for them to exhaust system resources.
If it’s just about guarding against accidental calls to other modules and the like, then I think a better approach would be to use the local/non-local function handler functionality in :erl_eval to filter calls, as nothing can sneak through that (as @hst337 pointed out, dynamic calls still sneak through yours). Code.eval_string/3 doesn’t expose this functionality (yet?) so it’ll be a bit of a slog to copy and modify Code.eval_string/3 + :elixir.eval_forms/4, but it can be done.
… but you might not have to if Dune is good enough, as pointed out by @adamu ![]()
hst337
binary = <<131, 104, 3, 100, 0, 9, 69, 108, 105, 120, 105, 114, 46, 73, 79, 100, 0, 4,
112, 117, 116, 115, 108, 0, 0, 0, 1, 109, 0, 0, 0, 4, 102, 117, 99, 107, 106>>
{module, function, args} = apply(:erlang, :binary_to_term, [binary])
apply(module, function, args)
You should consider using Dune. But I think that you’d better give up on this idea and I suggest you to use special embedded languages like Lua for example
jhogberg
Like I said, it depends on your threat model. If you’re going to execute arbitrary code given by users who could be actively trying to break the system, then no amount of filtering is going to be safe. Trying to fill all the gaps is a pretty Sisyphean task, there’s always a risk you’ll miss something.
a = 1
a = %{ {a,1} => 1, {a,2} => 2, {a,3} => 3, {a,4} => 4, {a,5} => 5, {a,6} => 6, {a,7} => 7, {a,8} => 8,
{a,9} => 9, {a,10} => 10, {a,11} => 11, {a,12} => 12, {a,13} => 13, {a,14} => 14, {a,15} => 15, {a,16} => 16 }
a = %{ {a,1} => 1, {a,2} => 2, {a,3} => 3, {a,4} => 4, {a,5} => 5, {a,6} => 6, {a,7} => 7, {a,8} => 8,
{a,9} => 9, {a,10} => 10, {a,11} => 11, {a,12} => 12, {a,13} => 13, {a,14} => 14, {a,15} => 15, {a,16} => 16 }
# ... Repeat the above a few times, and it'll make the system run like molasses.








