Welcome to the wonderful world of clojail! Clojail is a library for sandboxing Clojure code. Why is this useful? Well, for tons of reasons. Clojurians have used this library for a number of different things. Personally, I've used the library in two of my own projects: Try Clojure and lazybot.
If you have any questions, ideas, or feedback, shoot me a message here on Github or in an email. Feedback of all kind is welcome!
WARNING As of the 0.6.0 release, clojail is no longer compatible with Clojure 1.2.1. You should absolutely be using the latest version of clojail though so please do not stick with old versions. It's time to upgrade boys and girls.
You can get this library from clojars via Leiningen.
First, go to clojars and see what the latest version is.
Next, add it to your project.clj. After that, just run lein deps
.
Because clojail employs the JVM's built in sandboxing, you'll need to
have a ~/.java.policy
file to define permissions for your own
code. If you don't do this, you'll get security exceptions. I've
included a very liberal example.policy
file that you can just copy
over to ~/.java.policy
.
Once you've got that set up, you can play with the sandbox. Let's create a sandbox:
(ns my.project
(:use clojail.core)) ; Pull in the library.
(def tester #{'alter-var-root java.lang.Thread}) ; Create a blacklist.
(def sb (sandbox tester :timeout 5000))
You have just created a new sandbox. This sandbox will trigger whenever java.lang.Thread or alter-var-root is used in code. Anything else will pass the sandbox and be executed. Simple enough, right? Also, the :timeout bit is an optional argument. We've just lowered the timeout time to 5000 milliseconds, or 5 seconds. The default is 10000.
This sandbox isn't very secure. Let's create a new sandbox using the secure-tester in clojail.testers. This sandbox should be more secure. Can't promise total security however, since I can't test everything.
(ns my.project
(:use [clojail core testers]))
(def sb (sandbox secure-tester))
Don't bother setting :timeout
unless you just want to. The sandbox
has a reasonable default.
Alright, cool. Now we have a supposedly secure sandbox. How do we use
it? Simple! sb
is now bound to a function that takes code and
executes it in the sandbox.
(sb '(+ 3 3)) ; Returns 6
(sb '(def x)) ; Fails because def is not allowed in our sandbox.
Play around with it a bit.
(sb '(println "blah")) ; Returns nil
Wait... nil? It's doing the right thing and printing output to out and returning nil. However, for our purposes, maybe we want to get the output of sb as a string. Luckily, the sandbox accounts for that possibility and allows for you to provide, after the code to execute, a hashmap of vars to values. Here is how we can use that to get the output of sb as a string:
(let [writer (java.io.StringWriter.)]
(sb '(println "blah") {#'*out* writer}) (str writer)) ; Returns "blah\n"
There we go! Great! This only works for vars that are explicitly dynamic, just like
normal binding
.
Well, that's about all folks. I hope our library is to your liking, and I hope it's useful in your own projects. Remember to not hesitate to give us feedback! We especially like to hear how people are using sandboxing.
A tester is a set of objects, usually symbols, packages, and classes, that is considered as a blacklist and used to test if code is bad.
A nice feature of clojail is that you can blacklist entire Java packages. Don't want anything in the java.lang.reflect package? Fine:
(use '[clojail.testers :only [p]])
(def reflect-blacklist #{(p "java.lang.reflect")})
Now you have a tester that will scream rape if someone tries to execute code using any classes from the reflect package.
As of the 0.6.0 release of clojail, you can put functions in testers with the caveat that they must be serializable. This means you have to use serializable-fn. It is nice in that it can even serialize (most) closures correctly, so as long as your fns are not too complex, they should work just fine.
This allows for some added flexibility. An example of this new feature is the new blanket
function
in clojail.testers
. It allows you to blanket blacklist all namespaces under a common prefix.
It looks up the namespaces on the classpath using bultitude
and adds serializable functions to the tester to check for the prefix itself.
We can't promise that clojail will be entirely secure at any given time. Clojail uses a blacklist-based sandbox by default, so there will almost always be little holes in the sandbox. Fortunately, that only applies to the Clojure side of things. Since clojail is backed up by the JVM sandbox, even if the Clojure sandbox is broken, I/O still can't be done. Even if clojail breaks, the breaker can't wipe your system unless he has broken the JVM sandbox, in which case he has worked hard and earned his prize.
If somebody finds a hole in your Clojure sandbox, all they can do is break the state of the sandbox. Meaning, if they find a way to use 'eval', they can eval any code they like. That code will still be evaluated under the JVM sandbox. They can, however, use eval to call def and redefine stuff in the sandbox. This also means they can cause out-of-memory errors by defining a bunch of stuff. You'll want to prepare for such things.
We're considering making an effort to maintain a tester in
clojail.testers that tries to block out everything that's bad. The
reason we haven't undertaken that task so far is because we don't want
anybody to assume that the tester is totally secure, because you can
never really be certain. secure-tester
is unfortunately named. It
should be secure-enough-tester
or rather-secure-tester
. For the
most part, secure-tester is secure enough. Just be aware that there
will probably be holes, just not catastrophic ones.
Clojail is licensed under the same thing that Clojure is. See LICENSE.