Security Manager in Clojure

2019-08-07 10:17发布

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.

  1. Am I doing something wrong?
  2. 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?

1条回答
爷的心禁止访问
2楼-- · 2019-08-07 11:02

After adding a (println (.getName p)) to the first checkPermission() implementation, I found that the permissions that cause the loop are createClassLoader and suppressAccessChecks (see here for a detailed list on the permissions available). When evaluating a single expression in the REPL I got the following:

createClassLoader
createClassLoader
createClassLoader
suppressAccessChecks

Clojure creates a new DynamicClassLoader through RT.makeClassLoader() which is used in the following methods of the clojure.lang.Compiler class: eval, compile1 and load. When working on the REPL every expression is evaled so that triggers the createClassLoader permission and the suppressAccessChecks 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 a sun.reflect.DelegatingClassLoader is involved, so the permission createClassLoader 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 or createClassLoader. 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.

(def SM (proxy [SecurityManager] [] 
          (checkPermission 
            ([^java.security.Permission p] 
              ;; When there's no Reflection or ClassLoader creation
              ;; involved use Clojure code.
              (when-not (#{"suppressAccessChecks" "createClassLoader"} (.getName 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")))))))

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!

(def SM (proxy [SecurityManager] [] 
          (checkPermission 
            ([^java.security.Permission p] 
              (let [now (.toString ^Object (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")))))))
查看更多
登录 后发表回答