Evaluating a string of code in Erlang at runtime
Table of Contents
Did you know that Erlang has the ability to read in a string representing a line of code to execute at runtime? It can parse it out, evaluate it and return the value.
Let's see how...
Evaluating Simple Expressions
At its most basic, we can just read any expression passed in and execute it.
Let's try passing in some simple arithmetic expressions, remembering that statements end in commas and functions with a period, so our strings need to include those punctuations:
Security Considerations
As might be expected though, there are some serious security pitfalls if you allow just anyone to execute an arbitrary line of code though.
A brief review of SQL injection attacks
Let's switch gears for a minute and talk about SQL injection attacks.
We've just created a UI where a user can just type in their username to see information about themselves. Behind the scenes, we simply take the username as they entered it, and plug it into a query that looks like this:
When it’s evaluated, it looks something like this, and it returns the record for the user to the page:
As long as the user plays nicely, everything okay. But what if they enter their name as gwinney; delete * from user_table
? Now the query that’s run ends up looking like this:
The solution to this is to sanitize all input, aka parameterize the query. I don’t want to dive too deeply into it here, but if we’ve done things the right way then the query looks more like this, which will of course fail because that crazy username doesn’t exist.
select * from user_table where user_name = 'gwinney; delete * from user_table'
What’s this have to do with Erlang?
Similarly, we can run into security issues with our expression code.
We’re allowed to include a call to any function – local functions as well as BIFs (Erlang’s built-in functions) and exported functions in other modules we’ve created – and it’ll parse and attempt to execute them.
If we make the above function accessible to the outside world, even indirectly, and the input isn’t sanitized, then we’ve handed over the ability for someone to directly call all kinds of functions they have no business calling. Oops.
So how do we prevent that?
Intercepting Local Function Calls
We can supply a function to erl_eval:exprs
through which all calls to local functions will be passed, and that’s where we can take additional actions.
Local functions are those in the same module, which can be called without specifying the module name. Though some BIFs don’t require a module name, like list_to_binary
, that's only because they’re auto-imported by the system – they’re still considered non-local.
There’s some new stuff in the code below – a function called handle_local_function
and a local function called get_random_number
(thanks xkcd). The handler function outputs an informational message and then handles the passed-in function name.
Let's run the module again and pass in some new expressions.
We can intercept local functions (which may not really exist, but the expression evaluator doesn’t know that) and redirect them as we please… or just spit out a message if the user tries to do something invalid:
Intercepting Non-Local Function Calls
Similarly, we can supply a function to erl_eval:exprs
through which all calls to non-local functions will be passed (anything outside of the current module, including BIFs and even the operators used in comparisons).
Here’s the code again, extended to handle non-local functions:
Note how we explicitly handle the >
and <
comparison operators that are part of the erlang module, how we can redirect non-existent functions to existing ones, and how we can display a message if a function is unsupported.
Greater than and less than comparisons are allowed, but not equality… because. Some functions are allowed, some aren’t, and some are redirected. In the last example below, an evil user tries to enact their nefarious plan to take part of the system down, but is foiled. :p
What's Next?
Good examples in Erlang can be hard to come by, and what you see here was a fair amount of trial and error. If you find yourself trying to parse code and execute it at runtime, maybe this’ll help.
Other resources to check out:
- Evaluating Erlang code stored in a string (touches on binding variables)
- Official docs: erl_scan, erl_parse, erl_eval (have a pot of coffee ready)
- Domain Specific Languages in Erlang (powerpoint presentation)
- Can parameterized statement stop all SQL injection? (a thread with more details)
Spread the Word