9

I've got one ClassLoader for trusted application code and a seperate ClassLoader for user-submitted (untrusted) code.

I want the user-submitted code to be restricted by the Security Manager. How do I check the caller origin from within the SecurityManager? See the psuedocode:

System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        if (/*caller class is not loaded by the trusted classloader*/) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

What I've tried already:

  • StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass().getClassLoader() checks for permissions first so it gives a stack overflow exception.

  • Thread.currentThread().getStackTrace()[2].getClassLoaderName() is insecure because it only gives the classloader name and not class object, if the untrusted loader's canonical name is the same as the trusted loader then that's a security issue.

Henk Schurink
  • 419
  • 6
  • 17
  • *checks for permissions first so it gives a stack overflow exception.* - is that not solvable by special-casing that permission check so it does not result in recursion? – the8472 Aug 24 '19 at 07:50
  • @the8472 Could you explain how you wanted to "special-case" it? Because you can't really determine which class is requesting the permission to use StackWalker. It could be the securitymanager, but it could also be malicious code. – Henk Schurink Aug 24 '19 at 22:51
  • Well, you simply get the stackwalker early enough that no malicious code exists and so you can ok that particular call during the startup phase without further checking. The security checks happen during `getInstance()`, so you only have to grant that once. – the8472 Aug 25 '19 at 13:24
  • 4
    The user submitted code should be in its own `ProtectionDomain`/`CodeSource` and thus have its own permissions (separate from your own code). Then you should be able to just let the `SecurityManager` do its job. – Slaw Aug 27 '19 at 11:08

2 Answers2

4

First, SecurityManager has a protected method getClassContext().
Your code would look like this:

System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        Class<?> caller = getClassContext()[1];
        ClassLoader ccl = caller.getClassLoader();
        if (ccl != null || ccl != getClass().getClassLoader()) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

Second, if you want to use a StackWalker, it is recommended that you reuse the StackWalker instance:

StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        Class<?> caller = walker.getCallerClass();
        ClassLoader ccl = caller.getClassLoader();
        if (ccl != null || ccl != getClass().getClassLoader()) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

Third, this will most likely not do what you want. Security checks are done all over the JDK, so the caller might be any amount of stack levels away, requiring you to check the entire stack (Hint: break at the second time you visit your SecurityManager in the stack).


Instead, define a policy (create a java policy file) where you grant your code all permissions and use the java.lang.SecurityManager.

If it is not possible to write your own policy file, you can also use Policy.setPolicy() to install your own implementation of java.security.Policy.

Some hints for implementing a java.security.Policy:

  • Override implies and both getPermissions methods. Seriously.
  • Catch your own ProtectionDomain of your Policy class. (private static final ProtectionDomain MY_PD = MyPolicy.class.getProtectionDomain())
  • Use a fast path if the check is for your own ProtectionDomain. Don't call other code in this case, otherwise you might end up with a StackOverflow.
Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
0

I found a temporary solution but it isn't perfect:

System.setSecurityManager(new SecurityManager() {
    public void checkPermission(Permission permission) {
        Class<?> caller = SecurityManager.class;
        Class<?>[] classContext = this.getClassContext();
        int loopAmount = 0;

        while (caller.getCanonicalName() == null
                 || !caller.getCanonicalName().startsWith("nl")) {
            caller = classContext[loopAmount];
            loopAmount++;
        }

        if (caller.getClassLoader() != trustedClassLoader) {
            throw new SecurityException("You do not have permissions.");
        }
    }
});

All of the untrusted classes' canonical names have to start with 'nl'. This is afaik the only way to get the caller class from the class context because the array position of the caller class isn't known and the last element of the context array is always the class with the main method.

Henk Schurink
  • 419
  • 6
  • 17