Proposal for a Java Policy Files Crafting Process

I’ve already written about the JVM security manager and why it should be used — despite it being rarely the case, if ever. However, just advocating for it won’t change the harsh reality unless some guidelines are provided to do so. This post has the ambition to be the basis of such a set of guidelines.

As a reminder, the JVM can run in two different modes, standard and sandboxed. In the former, all API are available with no restrictions; in the latter, some API calls deemed sensitive are forbidden. In that case, explicit permissions to allow some of those calls can be configured in a dedicated policy file.

Though running the JVM in sandbox mode is important, it doesn’t stop there. For example, executing only digitally-signed code is also part of securing the JVM. This post is the first in a two-part series regarding JVM security.

Description

The process is based on the principle of least privilege. That directly translates into the following process:

  1. Start with a blank policy file.
  2. Run the application.
  3. Check the thrown security exception.
  4. Add the smallest-grained permission possible in the policy file that allows you to pass step 2.
  5. Return to step 2 until the application can be run normally.

Relevant system properties include:

That sounds easy enough, but let’s go into detail to see how it works with an example.

A Case Study

As a sample application, we will be using the Spring Pet Clinic, a typical, albeit small Spring Boot application.

First Steps

Once the application has been built, launch it with the security manager:

java -Djava.security.manager -Djava.security.policy=jvm.policy -jar target/spring-petclinic-1.4.2.jar

This, of course, fails. The output is the following:

Exception in thread "main" java.lang.IllegalStateException:
    java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getProtectionDomain")
  at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:43)
  at org.springframework.boot.loader.JarLauncher.<init>(JarLauncher.java:37)
  at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:58)
Caused by: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getProtectionDomain")
  at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
  at java.security.AccessController.checkPermission(AccessController.java:884)
  at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
  at java.lang.Class.getProtectionDomain(Class.java:2299)
  at org.springframework.boot.loader.Launcher.createArchive(Launcher.java:117)
  at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:40)
  ... 2 more

Let’s add the permissions relevant to the above "access denied" exception to the policy file:

grant codeBase "file:target/spring-petclinic-1.4.2.jar" {
    permission java.lang.RuntimePermission "getProtectionDomain";
};

Notice the path pointing to the JAR. It prevents other potentially malicious archives to execute critical code. Onto the next blocker.

Exception in thread "main"
    java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.protocol.handler.pkgs" "read")

This can be fixed by adding the below line to the policy file:

grant codeBase "file:target/spring-petclinic-1.4.2.jar" {
    permission java.lang.RuntimePermission "getProtectionDomain";
    permission java.util.PropertyPermission "java.protocol.handler.pkgs", "read";
};

Next please!

Exception in thread "main"
    java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.protocol.handler.pkgs" "read")

Looks quite similar, but it needs a write permission in addition to the read one. Sure, it can be fixed by adding one more line, but there’s a shortcut available. Just specify all necessary attributes of the permission on the same line:

grant codeBase "file:target/spring-petclinic-1.4.2.jar" {
    permission java.lang.RuntimePermission "getProtectionDomain";
    permission java.util.PropertyPermission "java.protocol.handler.pkgs", "read,write";
};

Rinse and repeat. Without further ado, the (nearly) final policy can be found online: a whopping ~1800 lines of configuration for the Spring Boot Pet Clinic as an executable JAR.

Now that the general approach has been explained, it just needs to be followed until the application functions properly. The next section describes some specific glitches along the way.

Securing Java Logging

At some point, nothing gets printed in the console anymore. The command line just returns, and that’s it. Here comes the java.security.debug system property, described above, which helps resolve the issue:

java -Djava.security.manager -Djava.security.policy=jvm.policy
     -Djava.security.debug=access,stacktrace -jar target/spring-petclinic-1.4.2.jar

That yields the following stack:

java.lang.Exception: Stack trace
  at java.lang.Thread.dumpStack(Thread.java:1329)
  at java.security.AccessControlContext.checkPermission(AccessControlContext.java:419)
  at java.security.AccessController.checkPermission(AccessController.java:884)
  at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
  at java.util.logging.LogManager.checkPermission(LogManager.java:1586)
  at java.util.logging.Logger.checkPermission(Logger.java:422)
  at java.util.logging.Logger.setLevel(Logger.java:1688)
  at java.util.logging.LogManager.resetLogger(LogManager.java:1354)
  at java.util.logging.LogManager.reset(LogManager.java:1332)
  at java.util.logging.LogManager$Cleaner.run(LogManager.java:239)

It’s time for some real software engineering (also known as Google Search). The LogManager’s Javadoc tells about the LoggingPermission that needs to be added to the existing list of permissions:

grant codeBase "file:target/spring-petclinic-1.4.2.jar" {
  permission java.lang.RuntimePermission "getProtectionDomain";
  ...
  permission java.util.PropertyPermission "PID", "read,write";
  permission java.util.logging.LoggingPermission "control";
};

Securing the Reading of System Properties and Environment Variables

It’s even possible to watch Spring Boot log… until one realizes it’s made entirely of error messages about not being able to read a bunch of system properties and environment variables. Here’s an excerpt:

2017-01-22 00:30:17.118  INFO 46549 --- [           main] o.s.w.c.s.StandardServletEnvironment     :
  Caught AccessControlException when accessing system environment variable [logging.register_shutdown_hook];
  its value will be returned [null].
  Reason: access denied ("java.lang.RuntimePermission" "getenv.logging.register_shutdown_hook")
2017-01-22 00:30:17.118  INFO 46549 --- [           main] o.s.w.c.s.StandardServletEnvironment     :
  Caught AccessControlException when accessing system property [logging_register-shutdown-hook];
  its value will be returned [null].
  Reason: access denied ("java.util.PropertyPermission" "logging_register-shutdown-hook" "read")

I will spare you, dear readers, a lot of trouble: There’s no sense in configuring every property one by one, as JCache requires read and write permissions on all properties. So just remove every fine-grained PropertyPermission so far and replace it with a catch-all coarse-grained one:

permission java.util.PropertyPermission "*", "read,write";

Seems like security was not one of JCache's developers' first priorities. The following snippet is the code excerpt for javax.cache.Caching.CachingProviderRegistry.getCachingProviders():

if (System.getProperties().containsKey(JAVAX_CACHE_CACHING_PROVIDER)) {
    String className = System.getProperty(JAVAX_CACHE_CACHING_PROVIDER);
    ...
}

Wow, it reads all properties! Plus the next line makes it a little redundant, no?

As for environment variables, the Spring team seems to try to avoid developers' configuration issues related to case and checks every possible case combination, so there are a lot of different options.

Variables and Subdirectories

At one point, Spring’s embedded Tomcat attempts, and fails, to create a subfolder in the java.io.tmpdir folder.

java.lang.SecurityException: Unable to create temporary file
  at java.io.File.createTempFile(File.java:2018) ~[na:1.8.0_92]
  at java.io.File.createTempFile(File.java:2070) ~[na:1.8.0_92]
  at org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory.createTempDir(...)
  at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer(...)
  at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(...)
  at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(...)
  ... 16 common frames omitted

One could get away with that by "hard-configuring" the path, but that would just be a major portability issue. Permissions are able to use System properties.

The second issue is the subfolder: There’s no way of knowing the folder name, hence it’s not possible to configure it beforehand. However, file permissions accept any direct children or any descendant in the hierarchy; the former is set with jokers and the second with dashes. The final configuration looks like this:

permission java.io.FilePermission "${java.io.tmpdir}/-", "read,write,delete";

CGLIB Issues

CGLIB is used heavily in the Spring framework to extend classes at compile-time. By default, the name of a generated class:

[…] is composed of a prefix based on the name of the superclass, a fixed string incorporating the CGLIB class responsible for generation, and a hashcode derived from the parameters used to create the object.

Consequently, one is faced with the following exception:

java.security.AccessControlException: access denied ("java.io.FilePermission"
    "[...]/boot/autoconfigure/web/MultipartAutoConfiguration$$EnhancerBySpringCGLIB$$cb1b157aCustomizer.class"
    "read")
  at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) ~[na:1.8.0_92]
  at java.security.AccessController.checkPermission(AccessController.java:884) [na:1.8.0_92]
  at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) ~[na:1.8.0_92]
  at java.lang.SecurityManager.checkRead(SecurityManager.java:888) ~[na:1.8.0_92]
  at java.io.File.exists(File.java:814) ~[na:1.8.0_92]
  at org.apache.catalina.webresources.DirResourceSet.getResource(...)
  at org.apache.catalina.webresources.StandardRoot.getResourceInternal(...)
  at org.apache.catalina.webresources.Cache.getResource(Cache.java:62) ~[tomcat-embed-core-8.5.6.jar!/:8.5.6]
  at org.apache.catalina.webresources.StandardRoot.getResource(...)
  at org.apache.catalina.webresources.StandardRoot.getClassLoaderResource(...)
  at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(...)
  at org.apache.catalina.loader.WebappClassLoaderBase$PrivilegedFindClassByName.run(...)
  at org.apache.catalina.loader.WebappClassLoaderBase$PrivilegedFindClassByName.run(...)
  at java.security.AccessController.doPrivileged(Native Method) [na:1.8.0_92]
  at org.apache.catalina.loader.WebappClassLoaderBase.findClass()
  at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader.findClassIgnoringNotFound()
  at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass()
  at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(...)
  at java.lang.Class.forName0(Native Method) [na:1.8.0_92]
  at java.lang.Class.forName(Class.java:348) [na:1.8.0_92]

It looks like quite an easy file permission fix, but it isn’t. For whatever reason, the hashcode used by CGLIB to extend MultipartAutoConfiguration changes at every compilation. Hence, a more lenient generic permission is required:

permission java.io.FilePermission "src/main/webapp/WEB-INF/classes/org/springframework/boot/autoconfigure/web/*", "read";

Launching Is Not the End

Unfortunately, once the application has been successfully launched, that doesn’t mean it stops there. Browsing the home page yields a new bunch of security exceptions.

For example, Tomcat needs to bind to port 8080, but this is a potentially unsecure action:

java.security.AccessControlException: access denied ("java.net.SocketPermission" "localhost:8080" "listen,resolve")

The permission to fix it is pretty straightforward:

permission java.net.SocketPermission "localhost:8080", "listen,resolve";

However, actually browsing the app brings a new exception:

java.security.AccessControlException: access denied ("java.net.SocketPermission" "[0:0:0:0:0:0:0:1]:56733" "accept,resolve")

That wouldn’t be bad if the port number didn’t change with every launch. A few attempts reveal that it seems to start from around 55400. Good thing that the socket permission allows for a port range:

permission java.net.SocketPermission "[0:0:0:0:0:0:0:1]:55400-", "accept,resolve";

Lessons Learned

Though it was very fulfilling to have created the policy file, the true value lies in the lessons learned.

In all cases, running the JVM in sandbox mode is not an option in security-aware environments.

 

 

 

 

Top