Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluate an arbitrary symbolic expression given as a string at some values

Tags:

r

I would like to let a user enter a string, for a formula for y as a function of x, e.g.

fn <- "x^2 + exp(3*x)"

I would then like to evaluate this expression at some points, e.g. for values of x <- 1:0.1:100.

I know I can do:

x <- 1:0.1:100
y <- eval(parse(text = fn))

However, this leaves my computer open to attacks if the user supplies fn which is not a formula

fn <- 'x; print("Your computer is compromised")'

Are there any alternative ways of achieving what I want to do?

like image 267
Alex Avatar asked Jun 08 '16 01:06

Alex


People also ask

How to evaluate an expression represented by a string?

Evaluate an expression represented by a String. The expression can contain parentheses, you can assume parentheses are well-matched. For simplicity, you can assume only binary operations allowed are +, -, *, and /. Arithmetic Expressions can be written in one of three forms:

How do you evaluate an expression with a symbolic variable?

When you assign a value to a symbolic variable, expressions containing the variable are not automatically evaluated. Instead, evaluate expressions by using subs. Define the expression y = x^2. The value of y is still x^2 instead of 4. If you change the value of x again, the value of y stays x^2.

How to evaluate a string using the eval function?

Since the eval function is not capable of evaluating character strings, we need to convert the character class to the expression class first. We can do that with the parse function as follows: As before the evaluation of our expression results in the value 15. Looks good!

How to eval an expression in R?

If we want to get the result of this expression (i.e. evaluate the expression), we can use the eval command as follows: As you can see, the eval function returned 15 (the result of 3 * 5) to the RStudio console. Expressions are often stored as R character string. Let’s create such a character string in R:


Video Answer


1 Answers

One of R's coolest features is that it can process its own language, so you can create a function white list and check expressions against it:

# Function to check if an expression is safe
is_safe_call <- function(text, allowed.funs) {
  # Recursive function to evaluate each element in the user expression
  safe_fun <- function(call, allowed.funs) {
    if(is.call(call)) {
      call.l <- as.list(call)
      if(!is.name(call.l[[1L]]) || !as.character(call.l[[1L]]) %in% allowed.funs)
        stop(as.character(call.l[[1L]]), " is not an allowed function")
      lapply(tail(call.l, -1L), safe_fun, allowed.funs)
    }
    TRUE
  }
  # parse and call recursive function
  expr <- parse(text=text)
  if(length(expr) > 1L) stop("Only single R expressions allowed")
  safe_fun(expr[[1L]], allowed.funs)
}

We then define a whitelist of allowable functions. It is very important that you are extremely careful about what you allow in here. In particular, if you allow parse, eval, or any functions with possible unpleasant side effects (system, unlink, etc.) you open your system wide for attack.

allowed.funs <- c("+", "exp", "*", "^", "-", "sqrt")

And we test:

is_safe_call("x^2 + exp(3*x)", allowed.funs)
## [1] TRUE
is_safe_call("x^2 - sqrt(3*x)", allowed.funs)
## [1] TRUE
is_safe_call("unlink('~', recursive=TRUE)", allowed.funs)
## Error in safe_fun(parse(text = text)[[1L]], allowed.funs) : 
##   unlink is not an allowed function
is_safe_call("x + sqrt(unlink('~', recursive=TRUE))", allowed.funs)
## Error in FUN(X[[i]], ...) : unlink is not an allowed function
is_safe_call('x; print("Your computer is compromised")')
## Error in is_safe_call("x; print(\"Your computer is compromised\")") : 
##   Only single R expressions allowed

No waranties express or implied in this. There may be a way to hack this that I haven't thought of, so don't put this on a public facing server without extensive scrutiny, but I think this might just work.

Note that if someone can provide an expression that somehow hacks parse itself, then you can be compromised that way.


EDIT: Ben Bolker suggested a clever trick to try to hack this, but this function is robust to that:

is_safe_call("exp <- function(...) system(\"echo do bad stuff\")", allowed.funs)
## Error in safe_fun(expr[[1L]], allowed.funs) :
##  <- is not an allowed function
allowed.funs <- c("+", "exp", "*", "^", "-", "sqrt", "<-")
is_safe_call("exp <- function(...) system(\"echo do bad stuff\")", allowed.funs)
## Error in FUN(X[[i]], ...) : function is not an allowed function
allowed.funs <- c("+", "exp", "*", "^", "-", "sqrt", "<-", "function")  
is_safe_call("exp <- function(...) system(\"echo do bad stuff\")", allowed.funs)
## Error in FUN(X[[i]], ...) : system is not an allowed function
like image 151
BrodieG Avatar answered Sep 21 '22 13:09

BrodieG