How can I program defensively in Ruby?
Asked Answered
M

9

5

Here's a perfect example of the problem: Classifier gem breaks Rails.

** Original question: **

One thing that concerns me as a security professional is that Ruby doesn't have a parallel of Java's package-privacy. That is, this isn't valid Ruby:

public module Foo
  public module Bar
    # factory method for new Bar implementations
    def self.new(...)
      SimpleBarImplementation.new(...)
    end
    def baz
      raise NotImplementedError.new('Implementing Classes MUST redefine #baz')
    end
  end

  private class SimpleBarImplementation
    include Bar
    def baz
      ...
    end
  end
end

It'd be really nice to be able to prevent monkey-patching of Foo::BarImpl. That way, people who rely on the library know that nobody has messed with it. Imagine if somebody changed the implementation of MD5 or SHA1 on you! I can call freeze on these classes, but I have to do it on a class-by-class basis, and other scripts might modify them before I finish securing my application if I'm not very careful about load order.

Java provides lots of other tools for defensive programming, many of which are not possible in Ruby. (See Josh Bloch's book for a good list.) Is this really a concern? Should I just stop complaining and use Ruby for lightweight things and not hope for "enterprise-ready" solutions?

(And no, core classes are not frozen by default in Ruby. See below:)

require 'md5'
# => true
MD5.frozen?
# => false
Milne answered 28/8, 2008 at 14:7 Comment(0)
G
1

Check out Immutable by Garry Dolley.

You can prevent redefinition of individual methods.

Godless answered 2/10, 2008 at 7:57 Comment(1)
It's not quite the same, but it does work pretty well. So does simply freezing the classes right after defining them. You still get namespace clutter, though...Milne
J
9

I don't think this is a concern.

Yes, the mythical "somebody" can replace the implementation of MD5 with something insecure. But in order to do that, the mythical somebody must actually be able to get his code into the Ruby process. And if he can do that, then he presumably could also inject his code into a Java process and e.g. rewrite the bytecode for the MD5 operation. Or just intercept the keypresses and not actually bother with fiddling with the cryptography code at all.

One of the typical concerns is: I'm writing this awesome library, which is supposed to be used like so:

require 'awesome'
# Do something awesome.

But what if someone uses it like so:

require 'evil_cracker_lib_from_russian_pr0n_site'
# Overrides crypto functions and sends all data to mafia
require 'awesome'
# Now everything is insecure because awesome lib uses 
# cracker lib instead of builtin

And the simple solution is: don't do that! Educate your users that they shouldn't run untrusted code they downloaded from obscure sources in their security critical applications. And if they do, they probably deserve it.

To come back to your Java example: it's true that in Java you can make your crypto code private and final and what not. However, someone can still replace your crypto implementation! In fact, someone actually did: many open-source Java implementations use OpenSSL to implement their cryptographic routines. And, as you probably know, Debian shipped with a broken, insecure version of OpenSSL for years. So, all Java programs running on Debian for the past couple of years actually did run with insecure crypto!

Judas answered 28/8, 2008 at 23:9 Comment(0)
T
4

Java provides lots of other tools for defensive programming

Initially I thought you were talking about normal defensive programming, wherein the idea is to defend the program (or your subset of it, or your single function) from invalid data input.
That's a great thing, and I encourage everyone to go read that article.

However it seems you are actually talking about "defending your code from other programmers."

In my opinion, this is a completely pointless goal, as no matter what you do, a malicious programmer can always run your program under a debugger, or use dll injection or any number of other techniques.

If you are merely seeking to protect your code from incompetent co-workers, this is ridiculous. Educate your co-workers, or get better co-workers.

At any rate, if such things are of great concern to you, ruby is not the programming language for you. Monkeypatching is in there by design, and to disallow it goes against the whole point of the feature.

Tactical answered 28/8, 2008 at 23:34 Comment(0)
A
1

I guess Ruby has that a feature - valued more over it being a security issue. Ducktyping too.
E.g. I can add my own methods to the Ruby String class rather than extending or wrapping it.

Ailsun answered 28/8, 2008 at 14:48 Comment(0)
M
1

"Educate your co-workers, or get better co-workers" works great for a small software startup, and it works great for the big guns like Google and Amazon. It's ridiculous to think that every lowly developer contracted in for some small medical charts application in a doctor's office in a minor city.

I'm not saying we should build for the lowest common denominator, but we have to be realistic that there are lots of mediocre programmers out there who will pull in any library that gets the job done, paying no attention to security. How could they pay attention to security? Maybe the took an algorithms and data structures class. Maybe they took a compilers class. They almost certainly didn't take an encryption protocols class. They definitely haven't all read Schneier or any of the others out there who practically have to beg and plead with even very good programmers to consider security when building software.

I'm not worried about this:

require 'evil_cracker_lib_from_russian_pr0n_site'
require 'awesome'

I'm worried about awesome requiring foobar and fazbot, and foobar requiring has_gumption, and ... eventually two of these conflict in some obscure way that undoes an important security aspect.

One important security principle is "defense in depth" -- adding these extra layers of security help you from accidentally shooting yourself in the foot. They can't completely prevent it; nothing can. But they help.

Milne answered 29/8, 2008 at 3:5 Comment(0)
G
1

Check out Immutable by Garry Dolley.

You can prevent redefinition of individual methods.

Godless answered 2/10, 2008 at 7:57 Comment(1)
It's not quite the same, but it does work pretty well. So does simply freezing the classes right after defining them. You still get namespace clutter, though...Milne
L
1

If monkey patching is your concen, you can use the Immutable module (or one of similar function).

Immutable

Lithology answered 2/10, 2008 at 8:1 Comment(0)
M
1

You could take a look at Why the Lucky Stiff's "Sandbox"project, which you can use if you worry about potentially running unsafe code. http://code.whytheluckystiff.net/sandbox/

An example (online TicTacToe): http://www.elctech.com/blog/safely-exposing-your-app-to-a-ruby-sandbox

Mencher answered 2/10, 2008 at 8:8 Comment(0)
M
1

Raganwald has a recent post about this. In the end, he builds the following:

class Module
  def anonymous_module(&block)
   self.send :include, Module.new(&block)
  end
end

class Acronym
  anonymous_module do
    fu = lambda { 'fu' }
    bar = lambda { 'bar' }
    define_method :fubar do
      fu.call + bar.call
    end
  end
end

That exposes fubar as a public method on Acronyms, but keeps the internal guts (fu and bar) private and hides helper module from outside view.

Milne answered 1/12, 2008 at 17:0 Comment(0)
P
0

If someone monkeypatched an object or a module, then you need to look at 2 cases: He added a new method. If he is the only one adding this meyhod (which is very likely), then no problems arise. If he is not the only one, you need to see if both methods do the same and tell the library developer about this severe problem.

If they change a method, you should start to research why the method was changed. Did they change it due to some edge case behaviour or did they actually fix a bug? especially in the latter case, the monkeypatch is a god thing, because it fixes a bug in many places.

Besides that, you are using a very dynamic language with the assumption that programmers use this freedom in a sane way. The only way to remove this assumption is not to use a dynamic language.

Perceivable answered 18/11, 2008 at 6:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.