The Java Security Manager

Introduction

In the last post, the Java Standard Edition and Java Enterprise Edition security features were introduced. This is the second in a series of articles that introduces each Java security feature in depth. This article introduces the Java Security Manager and the sandbox it creates for a Java applications execution environment. Two months ago, JSSE and its use in building client server applications was introduced in [1] & [2].

Anyone who has ever run a Java Applet in a browser has used a Java Security Manager, probably unknowingly. Java Applets run in an isolated sandbox where they can’t harm the browser, the user’s computer, or anything else–beyond themselves. This is accomplished with the Java Security Manager. Itprevents an Applet from:

· Accessing the file system
· Opening socket connections to any host other than the web server from which it was downloaded
· Causing the browser to exit
· Many others.

Now, what you may not realize is that the same Java Security Manager can be used to secure applications running in server-side JVMs. A Java Security Manager can be used in a stand-alone application or a J2EE Container. This article explores how a Security Manager can be used to secure either of these. Some J2EE Containers, Websphere for example, provide a Security Manager. Older versions of J2EE Containers didn’t explicitly support the Java Security manage–it had to be manually added.

As with any security technology, this is one piece of the puzzle. Just because the Java Security Manager has been deployed, there is no guarantee your application is secure. Likewise, the catch-all, not responsible for any damage this information may cause at the bottom of every thinkmiddleware.com page applies here.

The Default Security Manager

All JRE implementations provide a default Security Managerjava.lang.SecurityManager. The Security Manager can be used to provide most of the functionality that is described here. However, there are some desirable behaviors that require implementing a custom Security Manager.

To enable the default Security Manager, use the -Djava.security.manager java argument:

java -Djava.security.manager YourAppClass

This results in the Java application, YourAppClass, starting in a JVM that is using the default Security Manager obeying the policy defined in $JRE_HOME/lib/security/java.policy see the Security Policy Files. Another way of saying this is that the YourAppClass JVM is now running in a sandbox that significantly limits what the Java code can do. Again, recall the Java Applet example. If you are running application code from a potentially unknown source you don’t want to give it free reign of your machine. In a similar fashion, the server-side, JVM running a J2EE Container or stand-alone application may be running a vendor application than you don’t fully trust or processing requests from enemy-hostiles beyond your control(i.e., users). In either case, it may be desirable to limit what the Java code is actually capable doing; if the application attempts to violate the security policy, an exception is thrown.

I have seen the Java Security Manager used in J2EE application server environments to add an extra layer of security and as essentially the only layer of security in companies to cheap to even buy firewalls.

Custom Security Managers

It is possible to create your own Java Security Manager class by extending java.lang.SecurityManager
. One may wish to do this to achieve behavior that is not addressed by the default security policy.

For example, denying permission when application code calls System.exit() may be desirable for a J2EE Application container JVM. The only way I have found to make this occur is to implement a custom Security Manager that explicitly throws a SecurityException() in checkExit(int). I could be missing something, but this does work.

public class TMSecurityManager extends SecurityManager
{
public TMSecurityManager()
{
super();
}
public void checkExit(int c)
{
super.checkExit(c);
throw new SecurityException();
}
}

I found a similar issue with spawning sub-processes. Even though permission is supposed to be denied by default, my tests still worked initially. So, I created a custom Security Manager to perform the desired behavior.

Security Policy Files

Policy files were first mentioned the Default Security Manager section. The Java Security Manager provides a policy-based, sandbox in the JVMs execution environment. The default Security Manager will use the default Security Policy File, $JRE_HOME/lib/security/java.policy, if no other Policy File is specified.

The Policy File defines a Security Policy for the sandbox. A Security Policy consists of a set of Permissions granted to a code base.

The default behavior is to essentially deny everything for classes outside of the Java libraries (code located in $JRE_HOME/lib & $JRE_HOME/lib/ext). Classes in these directories are granted all permissions. The code located in $JRE_HOME/lib/ext must explicitly be granted allpermissions in the policy file.

To explicitly grant all permissions to a piece of code, use the following:

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

As you can see, permissions are granted to Java code based upon location. It is also possible to grant Permissions to all Java code that would be loaded into a JVM by omitting a “codeBase” tag in the grant claus. It is also possible to grant permissions based upon code signing. With JAAS, it is possible to grant permissions to specific users; however, this has significant limitations.

For code outside of the Java Standard Edition libraries, the following permissions are granted by default:
· The ability to call Thread.stop().
· The ability to establish a listening end point on any port above 1024.
· The ability to read certain System Properties
o java.version
o java.vendor
o java.vendor.url
o java.class.version
o os.name
o others (see java.policy file in your JRE).

A custom policy file can be specified on the command line with the -Djava.security.policy=file command line parameter. This should be used in conjunction with the -Djava.security.manager parameter:

java Djava.security.managaer Djava.security.policy=my_app.policy

Custom policy files can also be defined in the $JRE_HOME/lib/security/java.security file.

Specifying a Java policy file on the command line as shown above adds the policy file to the list of policy files. So, the default policy file and the policy file my_app.policy would be used in our example.

In the following snippet of the java.security file, two policy files are specified: the default policy file and the user policy file.

# The default is to have a single system-wide policy file,
# and a policy file in the user’s home directory.
policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

The java.security file also specifieswhether or not a policy file can be passed in on the Java command line with the following entry:

# whether or not we allow an extra policy to be passed on the command line
# with -Djava.security.policy=somefile. Comment out this line to disable
# this feature.
policy.allowSystemProperty=true

The Sun & IBM JDKs also ship with a tool called policytool. This tool eliminates the need to manually manipulate policy files.

Permissions

Policies grant Permissions to Java code. A complete list of all Permissions that can be granted can be found here.

Requirements For A Secure JVM

So, suppose we have an internet-facing, J2EE Container that has the following requirements to limit exposure. It’s not important whether this is realistic or not–some of these requirements are probably very realistic–we’re trying to demonstrate how a Security Manager can accomplish the goal. In some cases, the Security Manager doesn’t address the requirement. In such cases, alternative solutions will be presented.

For the sack of an example, assume the following security requirements exist:

  • No TCP listening endpoints may be established
  • No TCP client connections may be opened to any endpoint
  • No UDP listening endpoints may be established
  • No UDP client traffic may be generated
  • No Multicast traffic may be sent
  • No Multicast traffic may be received
  • No application code may cause the JVM to exit
  • No application code may activiate the JVM Garbage Collection subsystem
  • No application code may get or set system property values outside what is allowed by the default security policy
  • No application code set a new Security Manager or directly interact with the Security Manager
  • No application code will be allowed to read or write to any files on the file system

To demonstrate how Java Security can accomodate these requirements, a custom Security Manager and Security Policy have been created. The Java class, TMSecurityManager.java is given below

A series of tests that demonstrate that each property has been satisfied will be presented.

· Establish TCP listening endpoint above port 1023
· TCP Client connection to remote host:port
· UDP Client
· UDP Server
· Multicast datagram send
· Multicast datagram recieve
· Calling System.exit()
· Calling System.gc()
· Calling System.setSystemProperty()
· Calling System.getSystemProperty()
· Calling System.setSecurityManager()
· Opening and reading from a file
· Opening and writing to a file.

The Security Manager

The following Custom Security Manager will be used:

package com.tm.sm;

public class TMSecurityManager extends SecurityManager
{
public TMSecurityManager()
{
super();
}
public void checkExit(int c)
{
super.checkExit(c);
throw new SecurityException(“Permission Denied:” + c);
}
public void checkPermission( RuntimePermission p,
Object c)
throws NullPointerException, SecurityException
{
super.checkPermission(p, c);
if( p instanceof RuntimePermission && c == new RuntimePermission(“setSecurityManager”))
{
throw new SecurityException(“Permission Denied: ” + p + ” ” + c);
}
}
public void checkExec(String c)
{
super.checkExec(c);
throw new SecurityException(“Permission Denied: ” + c);
}
}

Notice, that this class extends java.lang.SecurityManager.

Also, the checkExit(int) method has been overridden and now throws a security exception in all cases. This prevents anyone from calling System.exit(). This means the only way the JVM can exit is if all non-daemon Java threads have exited. I haven’t found another way of accomplishing this.

The checkPermission() method has also been overriden in TMSecurityManager; it first calls calls the super class to provide for all default behavior. Then, it expictly denies permission to set a new Security Manager class.

Finally, TMSecurityManager, explictly denies permission to execute all external programs by overriding checkExec(). It may be desirable to create custom permissions objects that allow the ability to execute certain programs.

The Default java.policy File:

This series of tests used the following $JRE_HOME/jre/lib/security/java.policy file. There is only one modified line; the line granting permission for all code to establish listening end points above port 1024 has been removed.

// Standard extensions get all permissions by default

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

// default permissions granted to all domains

grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See “http://java.sun.com/notes” for more information.
permission java.lang.RuntimePermission “stopThread”;

// allows anyone to listen on un-privileged ports
//permission java.net.SocketPermission “localhost:1024-“, “listen”;

// “standard” properies that can be read by anyone

permission java.util.PropertyPermission “java.version”, “read”;
permission java.util.PropertyPermission “java.vendor”, “read”;
permission java.util.PropertyPermission “java.vendor.url”, “read”;
permission java.util.PropertyPermission “java.class.version”, “read”;
permission java.util.PropertyPermission “os.name”, “read”;
permission java.util.PropertyPermission “os.version”, “read”;
permission java.util.PropertyPermission “os.arch”, “read”;
permission java.util.PropertyPermission “file.separator”, “read”;
permission java.util.PropertyPermission “path.separator”, “read”;
permission java.util.PropertyPermission “line.separator”, “read”;
permission java.util.PropertyPermission “java.specification.version”, “read”;
permission java.util.PropertyPermission “java.specification.vendor”, “read”;
permission java.util.PropertyPermission “java.specification.name”, “read”;
permission java.util.PropertyPermission “java.vm.specification.version”, “read”;
permission java.util.PropertyPermission “java.vm.specification.vendor”, “read”;
permission java.util.PropertyPermission “java.vm.specification.name”, “read”;
permission java.util.PropertyPermission “java.vm.version”, “read”;
permission java.util.PropertyPermission “java.vm.vendor”, “read”;
permission java.util.PropertyPermission “java.vm.name”, “read”;
};

Custom java.policy File:

The following Custom Java Policy was used while performing these tests.

grant codeBase “file://${privledged.jar.path}” {
permission java.lang.RuntimePermission “setSecurityManager”;
permission java.lang.RuntimePermission “createSecurityManager”;
};

This grants permissions to the test tool to create and set a custom Security Manager. Note, privledged.jar.path is a System Property that is set on the command line; so, -Dprivledged.jar.path=path_to_jar_file is passed into the JVM at the command line.

Another approach to setting the Custom Security Manager is to use: -Djava.security.manager=com.tm.sm.TMSecurityManager. This will have the same effect as explictly calling System.setSecurityManager( new com.tm.sm.TMSecurityManager()), but doesn’t require granting permissions to a subset of code to allocate the custom Security Manager. This would probably be the required approach, if the JVM being secured ran a third-party application without source code.

Establish TCP listening endpoint above port 1023

The following code snippet will establish a listening end point on port 5000 on localhost:

try {
System.out.println(“TCPServerTest starting”);
ServerSocket ss = new ServerSocket(port);
while(true)
{
Socket s = ss.accept();
OutputStream os = s.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);
bw.write(“ack from server\n”);
bw.flush();
bw.close();
osw.close();
os.close();
s.close();
}
} catch(Exception e) {
e.printStackTrace();
}

Ordinarily, this snippet of code would result in the JVM listening for incoming connections on localhost:5000 and this thread waiting in an accept() call for an incoming connection. However, if the following line is removed from the default java.policy file:

permission java.net.SocketPermission “localhost:1024-“, “listen”;

Then, this code will fail with the following SecurityException:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost:5000 listen,resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkListen(SecurityManager.java:1120)
at java.net.ServerSocket.bind(ServerSocket.java:318)
at java.net.ServerSocket.(ServerSocket.java:185)
at java.net.ServerSocket.(ServerSocket.java:97)
at com.tm.tests.TCPServerTest.test(TCPServerTest.java:21)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:23)

which results in the ServerSocket(5000) method call in the code snippet failing.

This is the first SecurityException shown; so, it makes sense to cover how to grant the permission that is needed. The default Security Policy file grants all code permisison to establish listening endpoints above port 1023; however, we may want to be more selective than that. Maybe only the com.tm.testTool package and its supporting classes in the com.tm.tests package should be able to establish a listening endpoint.

To accomplish this, the entire code base, which presumably is stored in a set of well-defined jar files, would be granted the permission that is listed above. To avoid any further exceptions being thrown, every class listed in the stack trace above must be granted the ‘java.net.SocketPermission “localhost: 1024-“, “listen”‘ permission. The bottom two classes, com.tm.testTool.SecurityTest and com.tm.tests.TCPServerTest would be covered by a grant that add to the custom Security Policy such as:

grant code “file://{some.jar.path}” {
permission java.net.SocketPermission “localhost:1024-“, “listen”;
};

The rest of the classes are part of the Java Standard Edition networking packages. These packages are granted all permissions by default. Likewise, the java extension libraries, javax.*, are granted all permissions in the default Security Policy.

TCP Client connection to remote host:port

The next test attempts to establish a TCP connection to a remote endpoint using the following code:

String host=”localhost”;
int port = 5000;
try {
Socket s = new Socket(host,port);
s.close();
} catch(Exception e) {
e.printStackTrace();
}

When this code runs with the custom Security Manager and Security Policy described earlier, the following Exception is thrown:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContxt.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1031)
at java.net.InetAddress.getAllByName0(InetAddress.java:1134)
at java.net.InetAddress.getAllByName(InetAddress.java:1072)
at java.net.InetAddress.getAllByName(InetAddress.java:1008)
at java.net.InetAddress.getByName(InetAddress.java:958)
at java.net.InetSocketAddress.(InetSocketAddress.java:124)
at java.net.Socket.(Socket.java:180)
at com.tm.tests.TCPClientTest.test(TCPClientTest.java:20)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:70)

If we were to globally grant everything the ‘java.net.SocketPermission “*”, “resolve”‘ permission. Then, the same piece of code would produce the following exception:

java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:5000 connect,resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1034)
at java.net.Socket.connect(Socket.java:514)
at java.net.Socket.connect(Socket.java:470)
at java.net.Socket.(Socket.java:367)
at java.net.Socket.(Socket.java:180)
at com.tm.tests.TCPClientTest.test(TCPClientTest.java:20)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:70)

Notice, it was able to resolve localhost this time. But, still wasn’t allowed to open a socket connection.

UDP Client

In this test, the following code snippet is used to attempt to send UDP Datagram:

int port = 5000;
String host = “localhost”;
try {
System.out.println(“UDPClientTest starting”);
DatagramSocket socket = new DatagramSocket(port, new InetSocketAddress(host,port).getAddress());
byte[] buf = new byte[BUF_LENGTH];
//Initialize buffer here…
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}

Upon running this code, the following AccessControlException is generated:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1031)
at java.net.InetAddress.getAllByName0(InetAddress.java:1134)
at java.net.InetAddress.getAllByName(InetAddress.java:1072)
at java.net.InetAddress.getAllByName(InetAddress.java:1008)
at java.net.InetAddress.getByName(InetAddress.java:958)
at java.net.InetSocketAddress.(InetSocketAddress.java:124)
at com.tm.tests.UDPClientTest.test(UDPClientTest.java:21)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:86)

UDP Server

The following code snippet attempts to establish a listening endpoint for incoming datagrams:

int port = 5000;
try {
System.out.println(“UDPServerTest starting”);
DatagramSocket socket = new DatagramSocket(port);
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
} catch (Exception e) {
e.printStackTrace();
}

Running this code produces the following Exception:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost:5000 listen,resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkListen(SecurityManager.java:1120)
at java.net.DatagramSocket.bind(DatagramSocket.java:365)
at java.net.DatagramSocket.(DatagramSocket.java:210)
at java.net.DatagramSocket.(DatagramSocket.java:261)
at java.net.DatagramSocket.(DatagramSocket.java:234)
at com.tm.tests.UDPServerTest.test(UDPServerTest.java:19)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:77)

Multicast Datagram Send

The following code snippet will attempt to send a multicast message:

int port = 5000;
String address = “224.0.0.1”;
try {
System.out.println(“MulticastSendTest”);
MulticastSocket s = new MulticastSocket(port);
s.joinGroup(InetAddress.getByName(address));
byte buf[] = new byte[BUF_SIZE];
for(int i = 0; i<BUF_SIZE; ++i)
{
buf[i]=”;
}
DatagramPacket packet = new DatagramPacket(buf, buf.length);
System.out.write(packet.getData(),0,packet.getLength());
s.leaveGroup(InetAddress.getByName(address));
s.close();
} catch(Exception e) {
e.printStackTrace();
}

Running this code produces the following Exception:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost:5000 listen,resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkListen(SecurityManager.java:1120)
at java.net.DatagramSocket.bind(DatagramSocket.java:365)
at java.net.MulticastSocket.(MulticastSocket.java:147)
at java.net.MulticastSocket.(MulticastSocket.java:112)
at com.tm.tests.MulticastSendTest.test(MulticastSendTest.java:20)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:93)

Multicast Datagram Recieve

The following code snippet will attempt to send a Multicast datagram:

String address=”224.0.0.1″;
int port = 5000;
public void test()
{
try
{
System.out.println(“MulticastRecvTest running”);
MulticastSocket s = new MulticastSocket(port);
s.joinGroup(InetAddress.getByName(address));
byte buf[] = new byte[BUF_SIZE];
for(int i = 0; i<BUF_SIZE; ++i)
{
buf[i]=”;
}
DatagramPacket packet = new DatagramPacket(buf, buf.length);
s.receive(packet);
System.out.write(packet.getData(),0,packet.getLength());
s.leaveGroup(InetAddress.getByName(address));
s.close();
} catch(Exception e) {
e.printStackTrace();
}

Running this code in a JVM running our Security Manager will produce the following Exception:

java.security.AccessControlException: access denied (java.net.SocketPermission localhost:5000 listen,resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkListen(SecurityManager.java:1120)
at java.net.DatagramSocket.bind(DatagramSocket.java:365)
at java.net.MulticastSocket.(MulticastSocket.java:147)
at java.net.MulticastSocket.(MulticastSocket.java:112)
at com.tm.tests.MulticastRecvTest.test(MulticastRecvTest.java:21)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:99)

Calling System.exit()

The following code snipet will cause the JVM to exit:

try
{
System.out.println(“Calling System.exit()”);
System.exit(-1);
}
catch(Exception e)
{
e.printStackTrace();
}

Attempting to run this code, produces the following exception:

java.lang.SecurityException: Permission Denied: -1
at com.tm.sm.TMSecurityManager.checkExit(TMSecurityManager.java:48)
at java.lang.Runtime.exit(Runtime.java:88)
at java.lang.System.exit(System.java:869)
at com.tm.tests.ExitTest.test(ExitTest.java:10)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:28)

Calling System.gc()

Any developer can call System.gc() on a JVM to force the Virtual Machine to attempt to do a Garbage Collection. The key part of that sentence is “attempt” to do a garbage collection. There is no guarentee that the JVM will reclaim any memory.

From a middleware administrator perspective, it is desirable to limit the ability of developers ability to force garbage collections. In theory, the JVM should always be tuned for well-performing garbage collection. Typically, spending the time to find the optimal combination of heap size, generation size, garbage collection algorithm, and heap is impratical, but an 80-20 rule applies. You can spend short period of time getting eighty percent of the way there, but the last twenty would be unproductively prohibitive (in most business settings). Situations where this state cannot be reached are rare, but not impossible.

But, most of the time, if a developer is calling System.gc(), it is due to a lack of experience tuning garbage collection algorithms or laziness. So, one may wish to limit the ability to call System.gc(). Unfortunately, the SecurityManager doesn’t have a permission that can handle this.

However, most of the major JVM vendors provide a command line flag that will disable explicity garbage collection. This mechanism can be used to achieve the desired result; even if a develop calls System.gc(), it has no effect on the garbage collection sub-system.

In a Sun JVM, the ” -XX:+DisableExplicitGC” will disable calls to System.gc(); in an IBM JVM, “-Xdisableexplicitgc” will do the same.

In experiments for this article, time statistics gathered for the execution time of System.gc() showed the method taking ~15ms without the “-XX:+DisableExplicitGC” flag and ~0ms with the flag. This demonstrates the gc() method is not activiating the garbage collector.

So, we’ve achieved the desired behavior of disabling a developers ability programatically trigger garbage collections.

Calling System.setProperty()

The following code snippet attempts to overwrite the file.encoding System Property with an invalid character set name:

try {
System.out.println(“SetSystemPropertyTest starting”);
System.setProperty(“file.encoding”, “thinkmiddleware”);
System.out.println(“file.encoding: ” + System.getProperty(“file.encoding”));
} catch (Exception e) {
e.printStackTrace();
}

Running this code in our JVM produces the following Exception:

java.security.AccessControlException: access denied (java.util.PropertyPermission file.encoding write)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.System.setProperty(System.java:699)
at com.tm.tests.SetSystemPropertyTest.test(SetSystemPropertyTest.java:12)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:49)

So, it is no longer possible to modify System Properties such as file.encoding, which are very important to the correct function of Java code. I once spent several weeks tracking down a production issue that involved a developer modifying this parameter. It allowed their application to function properly, but other applications deployed to the same J2EE container began having problems. So, I have found it desirable to disallow run-time access of this parameter and most other System Properties.

It is also advisable to explictly set file.encoding at the command line, “-Dfile.encoding=ISO-8859-1”, to ensure the correct encoding table is being used.

There are numerous System Properties. The configuration being used disables write permissions for all of them. Depending on the application and environment, it may be necessary for the application to modify some system properties.

Calling System.getSystemProperty()

In a similar fashion, the following code will read the file.encoding property:

try {
System.out.println(“Read file.encoding property.”);
String property = System.getProperty(“file.encoding”);
System.out.println(“file.encoding: ” + property);
} catch (Exception e) {
e.printStackTrace();
}

Running this in our JVM will produce the following Exception:

java.security.AccessControlException: access denied (java.util.PropertyPermission file.encoding read)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
at java.lang.System.getProperty(System.java:628)
at com.tm.tests.GetSystemPropertyTest.test(GetSystemPropertyTest.java:11)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:56)

The configuration used here disables read access to many of the system properties. This will probably be overly restrictive for many real-world applications.

Calling System.setSecurityManager()

The following code attempts to create and set a foreign Security Manager. This means a Security Manager other than our com.tm.sm.TMSecurityManager.

BadSecurityManager s = new BadSecurityManager();
try {
System.out.println(“SetSecurityManagerTest starting.”);
System.setSecurityManager(s);
} catch (Exception e) {
e.printStackTrace();
}

When executed, the above code receives the following Exception:

java.security.AccessControlException: access denied (java.lang.RuntimePermission createSecurityManager)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.(SecurityManager.java:282)
at com.tm.tests.BadSecurityManager.(HostileSecurityManager.java:5)
at com.tm.tests.SetSecurityManagerTest.test(SetSecurityManagerTest.java:10)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:21)

So, it is not possible to allocate and set a new Security Manager, which is what we wanted to accomplish.

In order for the Test Tool to initially set the Security Manager, the custom Security Policy has to grant the permissions

java.lang.RuntimePermission “setSecurityManager”;
java.lang.RuntimePermission “createSecurityManager”;

must be granted to all classes mentioned in the above stack trace.

Opening and reading from a file

The following code will attempt to open and read the file “/bin/ls”:

String path=”/bin/ls”;
try
{
System.out.println(“ReadFileTest starting”);
FileInputStream fis = new FileInputStream(path);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line = null;
while( (line = br.readLine()) != null)
{
System.out.println(line);
}
br.close();
isr.close();
fis.close();
}
catch(Exception e)
{
e.printStackTrace();
}

Running this piece of code produces the following Exception:

java.security.AccessControlException: access denied (java.io.FilePermission /bin/ls read)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
at java.io.FileInputStream.(FileInputStream.java:100)
at java.io.FileInputStream.(FileInputStream.java:66)
at com.tm.tests.ReadFileTest.test(ReadFileTest.java:20)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:113)

Opening and writing to a file

The following code will attempt to open and write to “/tmp/tester”:

String path = “/tmp/tester”;
try
{
System.out.println(“WriteFileTest starting”);
FileOutputStream fos = new FileOutputStream(path);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
String line = null;
osw.write(“tester”,0,6);
bw.close();
osw.close();
fos.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}

Running this code in our JVM produces the following Exception:

java.security.AccessControlException: access denied (java.io.FilePermission /tmp/tester write)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkWrite(SecurityManager.java:962)
at java.io.FileOutputStream.(FileOutputStream.java:169)
at java.io.FileOutputStream.(FileOutputStream.java:70)
at com.tm.tests.WriteFileTest.test(WriteFileTest.java:20)
at com.tm.testTool.SecurityTest.main(SecurityTest.java:119)

Troubleshooting Permissions

Debugging permissions with the Security Manager is greatly simplified by adding:

-Djava.security.debug=all

This logs detailed information about permissions that are granted and activities surounding the Security Manager.

More information can be found here.

Closing Thoughts

This article has given an example of a custom Security Manager and Security Policy. It has shown several tests demonstrating various restrictions on Java application code.

Running the Java Security Manager has a significant performance penalty. This will be a factor in chosing to use it.

A J2EE Container such as Websphere ships with a custom Security Manager that greatly expands the capabilities and configuration options available. In a future article, we’ll explore this feature.

References

[1] http://thinkmiddleware.com/blog01/2008/08/27/part-1-creating-your-own-ssl-certificates-for-a-custom-java-client-server-application/
[2]
http://thinkmiddleware.com/blog01/2008/09/11/part-2-custom-client-server-java-application-that-communicatesover-a-mutually-authenticated-ssl-massl-connection/
[3] http://java.sun.com/applets/
[4] http://java.sun.com/j2se/1.5.0/docs/api/
[5]
http://java.sun.com/j2se/1.5.0/docs/guide/security/permissions.html
[6] http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/policytool.html
[6] http://java.sun.com/j2se/1.5.0/docs/api/java/lang/SecurityManager.html
[7] http://www.securingjava.com/toc.html
[8] http://java.sun.com/j2se/1.5.0/docs/guide/plugin/developer_guide/debugger.html#jsdp
[9] http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html

Leave a Reply

Your email address will not be published. Required fields are marked *