Bill Kelly wrote:
>> Sandboxing is made nearly impossible if calls in your own chain can 
>> escalate privileges by executing code from your binding.
> 
> Since you mention below JRuby doesn't support $SAFE, then I'm at a
> loss as to which sandboxing mechanism you refer.  (Does JRuby offer an
> alternative sandboxing approach?)

The JVM has its own built-in security mechanisms, and we do nothing to 
override or subvert them. We've considered adding portions of $SAFE like 
limiting eval and loading of code from the filesystem, but largely $SAFE 
is so amorphous and untested we've opted to leave it basically 
unimplemented. Also, a substantial portion of $SAFE's guarantees come 
from tainting, which requires checking taint on objects over and over 
and over again. I don't think it's a realistic or reliable security 
mechanism as it exists today.

> It seems to me there's trusted code, and untrusted code.  We allow trusted
> code to do all sorts of potentially dangerous things, including 
> reopening and
> redefining any method in any class, etc. etc.  So again, if we're 
> talking about
> trusted code, I'm hard pressed to see the validity of holding 
> Binding.of_caller
> up as an exemplar of something somehow more dangerous than the myriad
> privileges already granted to trusted code in ruby.

Without Binding.of_caller, no trusted or untrusted Ruby code can access 
a method's local variables unless that method passes a block or binding 
explicitly. of_caller opens up every method to that possibility. You 
don't think that exposure is problematic?

> Conversely, if we're talking about untrusted code, Binding.of_caller seems
> a relatively unremarkable addition to an already existing great number of
> avenues by which untrusted code can affect the system unless (somehow)
> sandboxed.

It goes well above and beyond existing mechanisms since it exposes 
details of a caller's bindings that would never be accessible through 
any other means.

Similar logic kicked retry out of 1.9, since it caused the original 
receiver and arguments to be reevaluated, whether the caller that 
behavior or not.

In general we should avoid adding the potential for locally-scoped side 
effects that no amount of inspection can detect. It opens up a big can 
of worms if you can't look at a method and know each step along the way 
that the variables are going to be what you set them do, and that no 
call you make will invalidate that expectation.

There's also logistical concerns. If you had Binding.of_caller 
available, how do you ensure you're not irreparably damaging the 
caller's context?

def do_call
   binding = Binding.of_caller
   eval "some_local_var = nil", binding
end

def innocent
   some_local_var = important_value
   do_call
   # now, without me assigning it, some_local_var has been changed
end

This is a horrible breach of trust that should never, ever be made 
possible in Ruby.

> It's thread local.  And it apparently somehow does get localized to a
> given binding (?) because ruby remembers the $SAFE level at which a
> lexical closure was compiled (or at least, a proc.)
> 
> For example, if a thread is at $SAFE=4, and calls a proc which was
> compiled at $SAFE=0, then the proc executes at $SAFE=0.
> 
> I do share your concerns about $SAFE never having been formally
> audited for security, and I'd agree it would be foolish to assume there
> don't exist some undiscovered holes in the system.
> 
> (On the other hand, in practical terms, we live with as yet undiscovered
> security holes in windows, linux, IIS, apache, etc. every day too.)

$SAFE is one place where we simply can't get by with the loose 
specifications in place for the rest of Ruby. I'd welcome discussions on 
how to make $SAFE or something similar a formally-specified security 
mechanism for Ruby. But as it stands now, I think it's too vague and 
requires too many little bits of code sprinkled all over the codebase to 
function correctly. I'm no security expert, but relying on a million 
tiny little taint checks to ensure the sandbox remains secure seems 
totally infeasible. If you have just *one* core method that doesn't 
propagate tainting correctly, you're screwed.

> That said, it appears $SAFE=4 was designed with the _intent_ to
> provide a secure sandbox.  And it seems to me the $SAFE mechanism
> could as easily disallow inappropriate uses of Binding.of_caller, just
> as it currently disallows the reopening of classes and modules, etc.

You're right about $SAFE locality...a binding does appear to keep a 
reference to its original thread's state, so it sees that thread's 
$SAFE. But of course that's not really applicable here, since $SAFE 
propagates down normal call stacks, and Binding.of_caller is intended 
for normal call stacks. So caller and callee would always have the same 
$SAFE level either way.

If you're talking about doing Binding.of_caller from within a proc body 
that came from another thread that has a different $SAFE level, with the 
caller calling Proc#call and the callee using that call to get the 
caller's binding with Binding.of_caller...I think we've probably got 
other complexity and threading issues to consider before we try to 
secure Binding.of_caller.

Here's an example of what you'd have to do to have a different caller 
$SAFE than callee:

puts "safe outside: #{$SAFE}"
x = nil
Thread.new { x = proc { puts "safe in proc: #{$SAFE}" } }.join
$SAFE = 1
puts "safe escalated: #{$SAFE}"
x.call
new_safe = eval "$SAFE", x
puts "safe from binding #{new_safe}"

This outputs 0, 1, 0, 1 as you'd expect. So the only way you'd 
potentially have a different safe level for a caller than for a callee 
is if you were evaluating code against a binding with a different save 
level (at which point we're explicitly using a binding anyway) or 
calling a proc (where it would be pretty rare to see Binding.of_caller). 
So I think $SAFE is not a particularly useful way to secure individual 
frames in the call stack.

> Again, I don't see why Binding.of_caller is particularly sharper than many
> other knives in ruby.  (If I try to imagine writing malicious code I 
> know will
> be executed with full privileges, Binding.of_caller isn't the first tool 
> that
> comes to mind when I contemplate all the nefarious possibilities.)

It's WAY sharper because it invalidates infallible truths about 
execution flow in a method body. Today, you can be guaranteed that 
unless you explicitly pass a binding to another call (either through 
eval/binding or by passing a block) none of your local variables will be 
mutated unless you mutate them yourself. Binding.of_caller breaks that 
assumption for all code everywhere. I don't want to live in that world.

- Charlie