Is 'eval' supposed to be nasty?
Asked Answered
C

8

27

I have been using the eval method in ruby many times. But I have heard people saying eval is nasty. When asked, why and how, I could never get a convincing reason not to use it. Are they really nasty? and if so, in what way? What are possible "safer" options to eval?

Chattel answered 12/3, 2009 at 4:55 Comment(4)
safer options depend on what you're using eval to do, can you be more specific?Flagstaff
There is nothing specific that I want to ask for. But by "safer" i meant something that gets you the same results avoiding or reducing the vulnerabilities.Chattel
is send generally safer to use than eval?Lemoine
send isn't really an alternative as send only sends a message to the object (that is, call a method). eval evaluates code. Any code.Chattel
H
39

If you are evaling a string submitted by, or modifiable by the user, this is tantamount to allowing arbitrary code execution. Imagine if the string contained an OS call to rm -rf / or similar. That said, in situations where you know the strings are appropriately constrained, or your Ruby interpreter is sandboxed appropriately, or ideally both, eval can be extraordinarily powerful.

The problem is analogous to SQL injection, if you're familiar. The solution here is similar to the solution to the injection problem (parameterized queries). That is, if the statements you would like to eval are known to be of a very specific form, and not all of the statement need be submitted by the user, only a few variables, a math expression, or similar, you can take in these small pieces from the user, sanitize them if necessary, then evaluate the safe template statement with the user input plugged in in the appropriate places.

Hairdresser answered 12/3, 2009 at 5:2 Comment(2)
And sanitizing has proved to be extraordinarily difficult to actually do properly for all cases. Nice answer!Cumin
rm -rf --no-preserve-root / nowadays.Bylaw
N
13

eval is not only insecure (as has been pointed out elsewhere), it's also slow. Every time it is executed, the AST of the evaled code needs to be parsed (and for eg JRuby, turned to bytecode) anew, which is a string-heavy operation and is also probably bad for cache locality (under the assumption that a running program doesn't eval a lot, and the corresponding parts of the interpreter are thus cache-cold, in addition to being large).

Why is there eval at all in Ruby, you ask? "Because we can" mostly - In fact, when eval was invented (for the LISP programming language), it was mostly for show! More to the point, using eval is The Right Thing when you want to "add an interpreter into your interpreter", for metaprogramming tasks such as writing a preprocessor, a debugger or a templating engine. The common idea for such applications is to massage some Ruby code and call eval on it, and it sure beats reinventing and implementing a domain-specific toy language, a pitfall also known as Greenspun's Tenth Rule. The caveats are: beware of the costs, eg for a templating engine, do all your evaling at startup time not run time; and don't eval untrusted code unless you know how to "tame" it, ie select and enforce a safe subset of the language according to the theory of capability discipline. The latter is a lot of really difficult work (see eg how that was done for Java; I'm not aware of any such effort for Ruby unfortunately).

Nicoline answered 27/8, 2012 at 10:10 Comment(1)
I wouldn't say it was invented "for show" in Lisp. It was first a construct in the realm of theoretical computer science which was then used as a blueprint of an interpreter (see the McCarty article on the site you are linking to).Language
K
12

In Ruby there are several gimmicks that might be more appropriate than eval():

  1. There is #send which allows you to call a method whose name you have as string and pass parameters to it.
  2. yield allows you to pass a block of code to a method which will be executed in the context of the receiving method.
  3. Often the simple Kernel.const_get("String") is sufficient to get the class whose name you have as string.

I think I am not able to explain them properly in detail, so I just gave you the hints, if you're interested you'll google.

Kobayashi answered 12/3, 2009 at 17:7 Comment(0)
I
7

It makes debugging difficult. It makes optimization difficult. But most of all, it's usually a sign that there is a better way to do whatever you are trying to do.

If you tell us what you are trying to accomplish with eval, you may get some more relevant answers relating to your specific scenario.

Immunochemistry answered 12/3, 2009 at 5:7 Comment(0)
C
6

Eval is an incredibly powerful feature which should be used carefully. Besides the security issues pointed out by Matt J, you will also find that debugging runtime evaluated code is extremely difficult. A problem in a runtime evaluated code block will be difficult for the interpreter to express - so looking for it will be difficult.

That being said, if you are comfortable with that issue, and are not concerned about the security issue, then you should not avoid using one of the features that makes ruby as appealing as it is.

Crayfish answered 12/3, 2009 at 5:9 Comment(0)
M
5

In certain situations, a well-placed eval is clever and reduces the amount of code required. In addition to the security concerns that have been mentioned by Matt J, you also need to ask yourself one very simple question:

When it's all said and done, can anyone else read your code and understand what you did?

If the answer is no, then what you've gained with an eval is forsaken for maintainability. This issue is not only applicable if you work in a team, but it is also applicable to you - you want to be able to look back at your code months, if not years from now, and know what you did.

Machicolation answered 12/3, 2009 at 5:15 Comment(0)
C
0

There's nothing inherently wrong with the eval method, however, requiring its use is often a sign of weak architecture design and design pattern decisions.

I have been working as a Ruby developer for 12 years full-time and I've never had to use eval in any of my repositories but when I found it in an existing repository it was always to bypass already existing complexity.

It also poses a huge security risk as any dynamic value is evaluated against the actual server using the applications user account.

Caryl answered 7/11, 2023 at 9:30 Comment(0)
I
-2

If you are passing anything that you get from the "outside" to eval, your are doing something wrong, and it's very nasty. It's very hard to escape the code enough for it to be safe, so I'd consider it quite unsafe. However, if you're using eval for avoiding duplication or other similar things, like the following code example, it's ok to use it.

class Foo
  def self.define_getters(*symbols)
    symbols.each do |symbol|
      eval "def #{symbol}; @#{symbol}; end"
    end
  end

  define_getters :foo, :bar, :baz
end

However, at least in Ruby 1.9.1, Ruby has really powerful meta-programming methods, and you could do the following instead:

class Foo
  def self.define_getters(*symbols)
    symbols.each do |symbol|
      define_method(symbol) { instance_variable_get(symbol) }
    end
  end

  define_getters :foo, :bar, :baz
end

For most purposes, you want to use these methods, and no escaping is needed.

The other bad thing about eval is the fact that (at least in Ruby), it's quite slow, as the interpreter needs to parse the string, and then execute the code inside the current binding. The other methods calls the C function directly, and therefore you should get quite a speed boost.

Innutrition answered 14/12, 2009 at 17:44 Comment(3)
define_method has been around for a long time — it's not a 1.9 feature. If you're using eval, it probably just means you don't know about the right tool for the job.Kimkimball
Oops, sorry, I see now that it wasn't clear at all what I meant. In Ruby 1.9 you have several new meta-programming methods, I didn't mean to refer specifically to define_method. But I agree with most of the other people that has answered the post, the OP should state why (s)he wants to use eval.Innutrition
-1. Your first define_getters can be a vulnerability! See a very similar example in https://mcmap.net/q/190103/-how-do-i-use-class_eval/…Anabantid

© 2022 - 2024 — McMap. All rights reserved.