I try to install a security manager
from a Clojure
program and am stuck short after a good start.
This one works for me in Clojures simple REPL
, in the Lein REPL, and in the Cider REPL in Emacs. It prevents the use of (System/exit n
).
(def SM (proxy [SecurityManager] []
(checkPermission
[^java.security.Permission p]
(when (.startsWith (.getName p) "exitVM")
(throw (SecurityException. "exit"))))))
(System/setSecurityManager SM)
But I want to do this security manager to do more. E.g. log any access
.
Surprisingly many Clojure functions seem to call the security manager, and thus, when used inside of it, give an infinite loop, aka StackOverflowException
.
My failed try:
(def security-log (atom ()))
(def SM (proxy [SecurityManager] []
(checkPermission
([^java.security.Permission p]
(let [now (.toString (System/currentTimeMillis))
log (str "[" now "] " "[cp/1] SM is asked for " p)]
(swap! security-log conj log))
(when (.startsWith (.getName p) "exitVM")
(throw (SecurityException. "exit"))))
([^java.security.Permission p ^Object o]
(let [now (.toString (System/currentTimeMillis))
log (str
"[" now "] "
"[cp/2] SM is asked for " p " on " o)]
(swap! security-log conj log))))))
(System/setSecurityManager SM)
Both failed, putting the log into an atom, and just printing it.
So I have two questions.
- Am I doing something wrong?
- Is there a systematic (and easy) way I can find out which functions I can use inside of a security manager, without putting it into a loop?
After adding a
(println (.getName p))
to the firstcheckPermission()
implementation, I found that the permissions that cause the loop arecreateClassLoader
andsuppressAccessChecks
(see here for a detailed list on the permissions available). When evaluating a single expression in the REPL I got the following:Clojure creates a new
DynamicClassLoader
throughRT.makeClassLoader()
which is used in the following methods of theclojure.lang.Compiler
class:eval
,compile1
andload
. When working on the REPL every expression iseval
ed so that triggers thecreateClassLoader
permission and thesuppressAccessChecks
are triggered by the use of reflection, which is common in Clojure.Still I couldn't figure out exactly how the loop was being generated so I added a
(Thread/dumpStack)
after the name printing and got this as the output. The gist represents only the first time it starts looping. The cause of the loop is that when performing reflection there seems to be an optimization (at least in Oracle's JVM and OpenJDK) where the initialization of asun.reflect.DelegatingClassLoader
is involved, so the permissioncreateClassLoader
is checked, the reflection for(.toString ,,,)
is done and so on...This makes sense since the first few times an expression is
eval
'ed in a fresh REPL the loop does not appear, only after a few times of evaluating the same expression the loop starts, which is when the optimization kicks in.The workaround I found involves using Clojure stuff only when the permission is not
suppressAccessChecks
orcreateClassLoader
. Filtering only one or the other causes the loop to appear, I'm pretty sure it might be related to the same issue but I didn't check the stack traces in those cases.EDIT
A much better solution is to avoid reflection in the code you include in the
checkPermission
method, which can be accomplished by just adding a type hint in the(.toString ,,,)
method call!