Ruby, SAFE, method interception, and plugins

Hi all,

I am working on an embedded Ruby application that may support user-written Ruby plugins in the future, and I am trying to get a rough idea as to what is and isn't possible, as it will affect the design I go with. Basically, if you've worked on such a thing before, please share your experiences. :slight_smile:

The ideal in my case is that the app will load in user-written Ruby code as a plugin, and the plugin author can interact with the rest of the code via a predefined and restricted set of objects and methods, but cannot mess with things outside of its environment, and especially not start exploring outside that environment in any way. I want to be able to completely lock the user out of using certain objects or classes.

Having not done anything like this in Ruby before, I'm trying to get a feel as to what might be possible and practical.

For example, many of the criteria in a $SAFE level of 4 seem appropriate to me- although what I'm really after is a way to lock things up, call some user code, and then revert back to a normal $SAFE level. It looks like the only way this could really be done though is to launch the plugin in its own (Ruby) thread and handle any synchronisation issues arising from it myself. Is this right?

Being able to set up my own access control by intercepting every method call made by the plugin would also be useful. I could then, for example, have a set of testing criteria that I could use on each method call to determine if it should be allowed (for example, class whitelists). I wonder if I could set up a series of proxy objects for objects that I'd like to wrap, but I wouldn't want a plugin author to use those proxy classes to get at the original objects, and mess with them directly. The interface could be as thin as two objects- an application object that you send messages to, and a plugin base object that the plugin uses to receive messages from the application. In this case I'd like the user to be able to create their own classes, manipulate strings and arrays (for example), but not interact with any of the remaining Ruby code at all, and certainly not examine it in any way.

As some app users might run plugins other app users have written, being able to limit the damage they can cause is also important. I'm not fussed if certain operations could cause a denial of service (eg. just run "while true; end"), as the environment would be such that the affected user could just kill the process and disable the plugin- it's not a web server. I *would* be fussed though if the plugin was able to read and write files to the system directly, or cause lasting damage outside of the application itself.

As you can tell, my thoughts on the subject are still somewhat disorganised. I'm trying to determine roughly which parts of this infrastructure would be easy, difficult, or impossible to implement. I would greatly benefit from the thoughts and experiences of people who have been down this road before. If you can find the time, please share your experiences, and let me know what worked, and what did not.

Thanks in advance, apologies for the long, rambling question. :slight_smile:

Garth

A quick addendum: I forgot to mention that I'm quite comfortable using Ruby, embedded Ruby, and the like- I just haven't worked on anything resembling this problem with Ruby before.

路路路

On 18/12/11 18:46, Garthy D wrote:

Hi all,

I am working on an embedded Ruby application that may support
user-written Ruby plugins in the future, and I am trying to get a rough
idea as to what is and isn't possible, as it will affect the design I go
with. Basically, if you've worked on such a thing before, please share
your experiences. :slight_smile:

The ideal in my case is that the app will load in user-written Ruby code
as a plugin, and the plugin author can interact with the rest of the
code via a predefined and restricted set of objects and methods, but
cannot mess with things outside of its environment, and especially not
start exploring outside that environment in any way. I want to be able
to completely lock the user out of using certain objects or classes.

Having not done anything like this in Ruby before, I'm trying to get a
feel as to what might be possible and practical.

For example, many of the criteria in a $SAFE level of 4 seem appropriate
to me- although what I'm really after is a way to lock things up, call
some user code, and then revert back to a normal $SAFE level. It looks
like the only way this could really be done though is to launch the
plugin in its own (Ruby) thread and handle any synchronisation issues
arising from it myself. Is this right?

Being able to set up my own access control by intercepting every method
call made by the plugin would also be useful. I could then, for example,
have a set of testing criteria that I could use on each method call to
determine if it should be allowed (for example, class whitelists). I
wonder if I could set up a series of proxy objects for objects that I'd
like to wrap, but I wouldn't want a plugin author to use those proxy
classes to get at the original objects, and mess with them directly. The
interface could be as thin as two objects- an application object that
you send messages to, and a plugin base object that the plugin uses to
receive messages from the application. In this case I'd like the user to
be able to create their own classes, manipulate strings and arrays (for
example), but not interact with any of the remaining Ruby code at all,
and certainly not examine it in any way.

As some app users might run plugins other app users have written, being
able to limit the damage they can cause is also important. I'm not
fussed if certain operations could cause a denial of service (eg. just
run "while true; end"), as the environment would be such that the
affected user could just kill the process and disable the plugin- it's
not a web server. I *would* be fussed though if the plugin was able to
read and write files to the system directly, or cause lasting damage
outside of the application itself.

As you can tell, my thoughts on the subject are still somewhat
disorganised. I'm trying to determine roughly which parts of this
infrastructure would be easy, difficult, or impossible to implement. I
would greatly benefit from the thoughts and experiences of people who
have been down this road before. If you can find the time, please share
your experiences, and let me know what worked, and what did not.

Thanks in advance, apologies for the long, rambling question. :slight_smile:

Garth

I'm not sure that I would want to rely on any language enforced constraints for executing 'hostile' code within the same address space as my main application. I think a better solution is to run the foreign code (that sounds nicer) in an external process or even on a completely separate system and then use some sort of communication scheme to interact with the foreign code. If the communication scheme is well defined it also means that the plugin doesn't have to even be in Ruby.

If you want to run it on the same system but in a different process you can arrange for the process to be 'locked down' in a sandbox or other restricted environment. The specifics on how to do this are going to be very dependent on your production environment but perhaps someone will pipe up with some specific suggestions if you tell us about your environment.

Gary Wright

路路路

On Dec 18, 2011, at 3:16 AM, Garthy D wrote:

As some app users might run plugins other app users have written, being able to limit the damage they can cause is also important. I'm not fussed if certain operations could cause a denial of service (eg. just run "while true; end"), as the environment would be such that the affected user could just kill the process and disable the plugin- it's not a web server. I *would* be fussed though if the plugin was able to read and write files to the system directly, or cause lasting damage outside of the application itself.

Garthy D 锌懈褋邪谢 18.12.2011 12:16:

Hi all,

(snip)

For example, many of the criteria in a $SAFE level of 4 seem
appropriate to me- although what I'm really after is a way to lock
things up, call some user code, and then revert back to a normal $SAFE
level. It looks like the only way this could really be done though is
to launch the plugin in its own (Ruby) thread and handle any
synchronisation issues arising from it myself. Is this right?

I want to note that $SAFE value is set per-thread[1], so you can load your
plugin into an anonymous namespace (Kernel#load second argument) and then
spawn a thread from there, using some library (Celluloid[2]?) to communicate
with the code in it.

I should say that I'm not sure that $SAFE is, well, safe for your needs, as
I'm not familiar with it at all.

  1: http://rxr.whitequark.org/mri/source/safe.c#040
  2: GitHub - tarcieri/celluloid: My personal fork of Celluloid. Please don't open PRs or issues here.

路路路

--
   WBR, Peter Zotov.

There's a number of folks using JRuby for this, most notable the
"Rails for Zombies" online course, which runs JRuby in a sandboxed
environment and allows students to run their code directly on the
server.

I am not a fan of $SAFE at all. I don't trust it, and I don't think
anyone else should either. The JVM's security model is far more
robust, and works well to secure a JRuby instance. There are many
examples of secure JVM-based services running major sites, such as all
of apps deployed to Google AppEngine for Java. I know of no example of
anyone running $SAFE mode in Ruby in a real-world setting.

I'm also looking to make JRuby's integration with the JVM security
model more robust in JRuby 1.7. At the very least, I'd like to have
equivalent built-in modes similar to SAFE levels that use JVM security
policies to enforce restrictions. Beyond that, I would like a full
complement of JVM permissions for Ruby-specific features like
evaluating code, reopening classes, and so on. You'll be able to
choose a pre-packaged SAFE-like policy, or roll your own.

I'd love to see Ruby adopt a real security model. Until then, I'll
keep trying to make JRuby utilize the JVM's model better.

- Charlie

路路路

On Sun, Dec 18, 2011 at 2:16 AM, Garthy D <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi all,

I am working on an embedded Ruby application that may support user-written
Ruby plugins in the future, and I am trying to get a rough idea as to what
is and isn't possible, as it will affect the design I go with. Basically, if
you've worked on such a thing before, please share your experiences. :slight_smile:

I haven't watched this presentation yet, so obviously can't comment on
the content, but it does sound like it's in the right ballpark:

http://confreaks.net/videos/713-rubyconf2011-sandboxing-ruby-the-good-the-bad-and-the-fugly

Hi Gary,

I'm not sure that I would want to rely on any language enforced constraints for executing 'hostile' code within the same address space as my main application. I think a better solution is to run the foreign code (that sounds nicer) in an external process or even on a completely separate system and then use some sort of communication scheme to interact with the foreign code. If the communication scheme is well defined it also means that the plugin doesn't have to even be in Ruby.

If you want to run it on the same system but in a different process you can arrange for the process to be 'locked down' in a sandbox or other restricted environment. The specifics on how to do this are going to be very dependent on your production environment but perhaps someone will pipe up with some specific suggestions

if you tell us about your environment.

I'll give it a shot. :slight_smile:

I am developing a cross-platform app, Linux and Windows initially. Using Ruby code for plugins is extremely desirable as much of the app itself is already written in Ruby, and I don't want the users to even have to consider the platform. Most users would only be using a single platform anyway, and many plugin authors would be new to Ruby.

From the perspective of a potential plugin, I would be providing the entire interface- every call that they could need to interact with the app would be provided. As mentioned, it could be as simple as message passing between a couple of objects. In fact, I don't want the plugin to be able to communicate outside the app, except through the API provided by the app. This includes networking, filesystem access, and the like. I would most likely supply a "require" replacement as well, and RubyGems or existing libraries wouldn't be directly usable. I would like plugin authors to be able to use things such as strings, arrays, create an manage their own custom classes, so forth. I could potentially whitelist allowed things if I could hook into things at various points.

Plugin authors would *generally* not be deliberately malicious, because they won't be entirely anonymous (as per if it was a online web app, for example), but it could happen, so I'd like to minimise the interactions with the external environment as much as possible. Some authors will be genuinely curious and be looking to explore the rest of the Ruby code that is running. I specifically want to limit this. A determined attacker will of course eventually succeed, but I want to make it difficult.

The app users would be the ones running the plugins. Most wouldn't be able to write a line of Ruby to save their lives, let alone understand plugins as anything more than a magic file they download. Thus, if they enabled a plugin that executed 99999999**999999999, it might lock the app up, but they'll just kill it, restart it, and not use the dodgy plugin next time. If the plugin was capable of blasting files away from their filesystem, then there is a problem. Thus maliciousness that causes an app crash is only a minor problem, maliciousness that deletes files is a big problem.

As most of the interaction would be in the form of method calls (ie. plugin calls app, expects a return value, app call plugin, expects a return value), a separate process for the app would be cumbersome. A separate thread would be slightly annoying, and in the same thread would be fine. However, I realise that the closer the plugin gets to the app in this way, the harder it will be to sandbox. My gut feeling is that the solution would probably be provided at the (Ruby) thread level.

The current state of the code is that the app is still in development, but many of the basics are in there. No plugin infrastructure exists yet, as I'm just starting to explore the possibilities (hence the reason for my post- I'm trying to figure out the best place to begin). The app itself is a C++ and embedded Ruby mix, and I've worked with both for the last decade or so. Patching the version of Ruby I am using myself is a possibility if it helps, as I'm fine building Ruby and potentially making very small changes. I am using the 1.9 series.

I'm happy answer any questions on specifics if it helps.

Garth

路路路

On 19/12/11 10:31, Gary Wright wrote:

Hi Peter,

Thanks for that- that confirms my current (somewhat limited) understanding of $SAFE thus far. Thanks for the reference to Celluloid, btw- this might help a bit. :slight_smile:

Garth

路路路

On 19/12/11 12:06, Peter Zotov wrote:

Garthy D 锌懈褋邪谢 18.12.2011 12:16:

Hi all,

(snip)

For example, many of the criteria in a $SAFE level of 4 seem
appropriate to me- although what I'm really after is a way to lock
things up, call some user code, and then revert back to a normal $SAFE
level. It looks like the only way this could really be done though is
to launch the plugin in its own (Ruby) thread and handle any
synchronisation issues arising from it myself. Is this right?

I want to note that $SAFE value is set per-thread[1], so you can load your
plugin into an anonymous namespace (Kernel#load second argument) and then
spawn a thread from there, using some library (Celluloid[2]?) to
communicate
with the code in it.

I should say that I'm not sure that $SAFE is, well, safe for your needs, as
I'm not familiar with it at all.

1: http://rxr.whitequark.org/mri/source/safe.c#040
2: GitHub - tarcieri/celluloid: My personal fork of Celluloid. Please don't open PRs or issues here.

Hi Charlie,

I have no realm experience with JRuby, although at a glance it might be a closer fit with respect to securing running plugins. I might be stuck in this regard though as a good chunk of the app is already written, and C++-based. The cost of moving across might prove to be too high in my particular case, but at the very least it gives me something to explore and think about. Thankyou for sharing- this is probably not an area I would have thought to investigate on my own. :slight_smile:

Garth

路路路

On 19/12/11 12:16, Charles Oliver Nutter wrote:

On Sun, Dec 18, 2011 at 2:16 AM, Garthy D > <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi all,

I am working on an embedded Ruby application that may support user-written
Ruby plugins in the future, and I am trying to get a rough idea as to what
is and isn't possible, as it will affect the design I go with. Basically, if
you've worked on such a thing before, please share your experiences. :slight_smile:

There's a number of folks using JRuby for this, most notable the
"Rails for Zombies" online course, which runs JRuby in a sandboxed
environment and allows students to run their code directly on the
server.

I am not a fan of $SAFE at all. I don't trust it, and I don't think
anyone else should either. The JVM's security model is far more
robust, and works well to secure a JRuby instance. There are many
examples of secure JVM-based services running major sites, such as all
of apps deployed to Google AppEngine for Java. I know of no example of
anyone running $SAFE mode in Ruby in a real-world setting.

I'm also looking to make JRuby's integration with the JVM security
model more robust in JRuby 1.7. At the very least, I'd like to have
equivalent built-in modes similar to SAFE levels that use JVM security
policies to enforce restrictions. Beyond that, I would like a full
complement of JVM permissions for Ruby-specific features like
evaluating code, reopening classes, and so on. You'll be able to
choose a pre-packaged SAFE-like policy, or roll your own.

I'd love to see Ruby adopt a real security model. Until then, I'll
keep trying to make JRuby utilize the JVM's model better.

- Charlie

Hi Mark,

I've just finished watching it- thanks for that. :slight_smile: The sort of problem the presenter was trying to solve is running untrusted web-submitted code, and has a degree of similarity with the problem I am trying to solve. I can't say that I now have a sense of knowing how to solve the problem entirely, but it was an interesting watch nonetheless. :slight_smile:

Thanks again. :slight_smile:

Garth

路路路

On 21/12/11 04:17, Mark Somerville wrote:

I haven't watched this presentation yet, so obviously can't comment on
the content, but it does sound like it's in the right ballpark:

http://confreaks.net/videos/713-rubyconf2011-sandboxing-ruby-the-good-the-bad-and-the-fugly

JRuby does support FFI, for calling C libraries, so one approach to
using your existing code would be to write a thin C wrapper and bind
it with FFI.

An alternative would be to write a thin JNI (Java Native Interface)
wrapper and use that from JRuby just like any Java API. The level of
effort would be similar to writing a Ruby C ext.

In any case, keep me posted. If you decide to go with JRuby, perhaps
we can collaborate on coming up with appropriate permissions and
security policies.

- Charlie

路路路

On Sun, Dec 18, 2011 at 8:48 PM, Garthy D <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi Charlie,

I have no realm experience with JRuby, although at a glance it might be a
closer fit with respect to securing running plugins. I might be stuck in
this regard though as a good chunk of the app is already written, and
C++-based. The cost of moving across might prove to be too high in my
particular case, but at the very least it gives me something to explore and
think about. Thankyou for sharing- this is probably not an area I would have
thought to investigate on my own. :slight_smile:

Garth

On 19/12/11 12:16, Charles Oliver Nutter wrote:

On Sun, Dec 18, 2011 at 2:16 AM, Garthy D >> <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi all,

I am working on an embedded Ruby application that may support
user-written
Ruby plugins in the future, and I am trying to get a rough idea as to
what
is and isn't possible, as it will affect the design I go with. Basically,
if
you've worked on such a thing before, please share your experiences. :slight_smile:

There's a number of folks using JRuby for this, most notable the
"Rails for Zombies" online course, which runs JRuby in a sandboxed
environment and allows students to run their code directly on the
server.

I am not a fan of $SAFE at all. I don't trust it, and I don't think
anyone else should either. The JVM's security model is far more
robust, and works well to secure a JRuby instance. There are many
examples of secure JVM-based services running major sites, such as all
of apps deployed to Google AppEngine for Java. I know of no example of
anyone running $SAFE mode in Ruby in a real-world setting.

I'm also looking to make JRuby's integration with the JVM security
model more robust in JRuby 1.7. At the very least, I'd like to have
equivalent built-in modes similar to SAFE levels that use JVM security
policies to enforce restrictions. Beyond that, I would like a full
complement of JVM permissions for Ruby-specific features like
evaluating code, reopening classes, and so on. You'll be able to
choose a pre-packaged SAFE-like policy, or roll your own.

I'd love to see Ruby adopt a real security model. Until then, I'll
keep trying to make JRuby utilize the JVM's model better.

- Charlie

Hi Charlie,

Thankyou for the additional information. :slight_smile: As mentioned there would be a big cost to moving across to JRuby at this point- but I now have a much better starting point if I undertook such a move. Thanks for that. :slight_smile: I wasn't familiar with FFI, but I'm quite familiar with the problem they are trying to solve. Very interesting stuff. :slight_smile: There are certainly other benefits to moving to a JVM-based approach as well- as well as associated disadvantages. I'll also assume that's it's pretty much a given that I could use have Java-based plugins as well if I went this way- and this could very well be a big plus. I've certainly got some things to think about now. :slight_smile:

Garth

路路路

On 19/12/11 13:35, Charles Oliver Nutter wrote:

JRuby does support FFI, for calling C libraries, so one approach to
using your existing code would be to write a thin C wrapper and bind
it with FFI.

An alternative would be to write a thin JNI (Java Native Interface)
wrapper and use that from JRuby just like any Java API. The level of
effort would be similar to writing a Ruby C ext.

In any case, keep me posted. If you decide to go with JRuby, perhaps
we can collaborate on coming up with appropriate permissions and
security policies.

- Charlie

On Sun, Dec 18, 2011 at 8:48 PM, Garthy D > <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi Charlie,

I have no realm experience with JRuby, although at a glance it might be a
closer fit with respect to securing running plugins. I might be stuck in
this regard though as a good chunk of the app is already written, and
C++-based. The cost of moving across might prove to be too high in my
particular case, but at the very least it gives me something to explore and
think about. Thankyou for sharing- this is probably not an area I would have
thought to investigate on my own. :slight_smile:

Garth

On 19/12/11 12:16, Charles Oliver Nutter wrote:

On Sun, Dec 18, 2011 at 2:16 AM, Garthy D >>> <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi all,

I am working on an embedded Ruby application that may support
user-written
Ruby plugins in the future, and I am trying to get a rough idea as to
what
is and isn't possible, as it will affect the design I go with. Basically,
if
you've worked on such a thing before, please share your experiences. :slight_smile:

There's a number of folks using JRuby for this, most notable the
"Rails for Zombies" online course, which runs JRuby in a sandboxed
environment and allows students to run their code directly on the
server.

I am not a fan of $SAFE at all. I don't trust it, and I don't think
anyone else should either. The JVM's security model is far more
robust, and works well to secure a JRuby instance. There are many
examples of secure JVM-based services running major sites, such as all
of apps deployed to Google AppEngine for Java. I know of no example of
anyone running $SAFE mode in Ruby in a real-world setting.

I'm also looking to make JRuby's integration with the JVM security
model more robust in JRuby 1.7. At the very least, I'd like to have
equivalent built-in modes similar to SAFE levels that use JVM security
policies to enforce restrictions. Beyond that, I would like a full
complement of JVM permissions for Ruby-specific features like
evaluating code, reopening classes, and so on. You'll be able to
choose a pre-packaged SAFE-like policy, or roll your own.

I'd love to see Ruby adopt a real security model. Until then, I'll
keep trying to make JRuby utilize the JVM's model better.

- Charlie