Wednesday, 21 August 2013

Safely evaluating arithmetic expressions in R?

Safely evaluating arithmetic expressions in R?

I am writing some code to take an arithmetic expression as user input and
evaluate it. I have a specified set of variables that can be used, and a
whitelist of arithmetic functions (+, -, *, /, ^, etc.). Is there any way
that I can evaluate an expression so that only these variables and
operators are in scope, in order to avoid any possibility of arbitrary
code injection? I have something that I think works, but I don't want to
actually use it unless I have some certainty that it is really
bulletproof:
## Shortcut for parse-then-eval pattern
evalparse <- function(expr, ...) eval(parse(text=expr), ...)
arithmetic.operators <- mget(c("(", "+", "-", "/", "*", "^", "sqrt",
"log", "log10", "log2", "exp", "log1p"), inherits=TRUE)
vars <- list(a=1, b=2)
safe.envir <- c(vars, arithmetic.operators)
# Assume that these expressions are user input, e.g. from a web form.
nice.expr <- "a + b"
naughty.expr <- paste("cat('R IS NOW PWNED\n'); system('echo SYSTEM IS NOW
PWNED');", nice.expr)
## NOT SAFE! Lookups outside env still possible.
evalparse(nice.expr, envir=safe.envir)
evalparse(naughty.expr, envir=safe.envir)
## Is this safe?
evalparse(nice.expr, envir=safe.envir, enclos=emptyenv())
evalparse(naughty.expr, envir=safe.envir, enclos=emptyenv())
If you run the above code in R, you'll see that the first time we eval
naughty.expr, it successfully executes its payload. However, the second
time, with enclose=emptyenv(), the evaluation only has access to the
variables a, b, and the specified arithmetic operators, so the payload
fails to execute.
So, is this method (i.e. eval(..., envir=safeenv, enclos=emptyenv()) )
actually OK to use in production accepting actual user input, or am I
missing some sneaky way to still execute arbitrary code in the resticted
environment?

No comments:

Post a Comment