/ JVM, SECURITY

The Java Security Manager: why and how?

Generally, security concerns are boring for developers. I hope this article is entertaining enough for you to read it until the end since it tackles a very serious issue on the JVM.<!--more-→

Quizz

Last year, at Joker conference, my colleague Volker Simonis showed a snippet that looked like the following:

public class StrangeReflectionExample {

    public Character aCharacter;

    public static void main(String... args) throws Exception {
        StrangeReflectionExample instance = new StrangeReflectionExample();
        Field field = StrangeReflectionExample.class.getField("aCharacter");
        Field type = Field.class.getDeclaredField("type");
        type.setAccessible(true);
        type.set(field, String.class);
        field.set(instance, 'A');
        System.out.println(instance.aCharacter);
    }
}

Now a couple of questions:

  1. Does this code compile?
  2. If yes, does it run?
  3. If yes, what does it display?

Answers below.

This code compiles just fine. In fact, it uses the so-called reflection API (located in the java.lang.reflect package) which is fully part of the JDK.

Executing this code leads to the following exception:

Exception in thread "main" java.lang.IllegalArgumentException:
        Can not set java.lang.String field ch.frankel.blog.securitymanager.StrangeReflectionExample.aCharacter
            to java.lang.Character
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
	at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
	at java.lang.reflect.Field.set(Field.java:764)
	at ch.frankel.blog.securitymanager.StrangeReflectionExample.main(StrangeReflectionExample.java:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

So, despite the fact that we defined the type of the aCharacter attribute as a Character at development time, the reflection API is able to change its type to String at runtime! Hence, trying to set it to 'A' fails.

Avoiding nasty surprises with the Security Manager

Reflection is not the only risky operation one might want to keep in check on the JVM. Reading a file or writing one also belong to the set of potentially dangerous operations. Fortunately, the JVM has a system to restrict those operations. Unfortunately, it’s not set by default.

In order to activate the SecurityManager, just launch the JVM with the java.security.manager system property i.e. java -Djava.security.manager. At this point, the JVM will use the default JRE policy. It’s configured in the file located at %JAVA_HOME%/lib/security/java.policy (for Java 8). Here’s a sample of this file:

grant codeBase "file:${java.ext.dirs}/*" {
        permission java.security.AllPermission;
};

grant {
        permission java.lang.RuntimePermission "stopThread";
        permission java.net.SocketPermission "localhost:0", "listen";
        permission java.util.PropertyPermission "java.version", "read";
        permission java.util.PropertyPermission "java.vendor", "read";
        ...
}

The first section - grant codeBase, is about which code can be executed; the second - grant, is about specific permissions.

Regarding the initial problem regarding reflection mentioned above, the second part is the most relevant. One can read the source of the AccessibleObject.setAccessible() method:

SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);

Every sensitive method to a Java API has the same check through the Security Manager. You can verify that for yourself in the following code:

  • Thread.stop()
  • Socket.bind()
  • System.getProperty()
  • etc.

Using an alternate java.policy file

Using the JRE’s policy file is not convenient when one uses the same JRE for different applications. Given the current micro-service trend, this might not be the case. However, with automated provisioning, it might be more convenient to always provision the same JRE over and over and let each application provides its own specific policy file.

To add another policy file in addition to the default JRE’s, thus adding more permissions, launch the JVM with:

java -Djava.security.manager -Djava.security.policy=/path/to/other.policy

To replace the default policy file with your own, launch the JVM with:

java -Djava.security.manager -Djava.security.policy==/path/to/other.policy
Note the double equal sign.

Configuring your own policy file

Security configuration can be either based on a:

Black list

In a black list scenario, everything is allowed but exceptions can be configured to disallow some operations.

White list

On the opposite, in a white list scenario, only operations that are explicitly configured are allowed. By default, all operations are disallowed.

If you want to create your own policy file, it’s suggested you start with a blank one and then launch your app. As soon, as you get a security exception, add the necessary permission is the policy. Repeat until you have all necessary permissions. Following this process will let you have only the minimal set of permissions to run the application, thus implementing the least privilege security principle.

Note that if you’re using a container or a server, you’ll probably require a lot of those permissions, but this is the price to pay to secure your JVM against abuses.

Conclusion

I never checked policy files in production, but since I never had any complain, I assume the JVM’s policy was never secured. This is a very serious problem! I hope this article will raise awareness regarding that lack of hardening - especially since with the latest JVM, you can create and compile Java code on the fly, leading to even more threats.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
The Java Security Manager: why and how?
Share this