When most people think of Java security, they think of the protections afforded to a Java program -- and, more particularly, only by default to a Java applet -- by Java’s security manager. There are other important facets of Java’s security story, but the role played by the security manager is of paramount importance in defining the security policy under which a particular program will operate.
On one level, the Java security manager is simple to understand, and it’s often summarized by saying that it prevents Java applets from accessing your local disk or local network. The real story is more complicated than that, however, with the result that Java’s security manager is often misunderstood.
On a simple level, the security manager is responsible for determining most of the parameters of the Java sandbox -- that is, it is ultimately up to the security manager to determine whether many particular operations should be permitted or rejected. If a Java program attempts to open a file, the security manager decides whether or not that operation should be permitted. If a Java program wants to connect to a particular machine on the network, it must first ask permission of the security manager. If a Java program wants to alter the state of certain threads, the security manager will intervene if such an operation is considered dangerous.
The
security manager is used only if it
is explicitly installed. When you run a Java application, specifying
the -Djava.security.manager
option installs a
security manager. The security manager is installed programatically
by the
appletviewer and the Java Plug-in.
Hence, this point cannot be overemphasized: Java applications (at least by default) have no security manager while Java applets (again, by default) have a very strict security manager. This leads to a common misconception that exists in the arena of Java security: it’s common to think that because Java is said to be secure, it is always secure and that running Java applications that have been installed locally is just as secure as running Java applets inside a Java-enabled browser. Nothing is further from the truth.
To illustrate this point, consider the following malicious code:
package javasec.samples.ch04; import java.applet.*; public class MaliciousApplet extends Applet { public void init( ) { try { Runtime.getRuntime( ).exec("rmdir foo"); } catch (Exception e) { System.out.println(e); } } public static void main(String args[]) { MaliciousApplet a = new MaliciousApplet( ); a.init( ); } }
If you compile this code, place it on your web server, and load it as
an applet, you will get an error reflecting a security violation.
However, if you compile this code, place it in a directory, and then
run it as an application (without using the
-Djava.security.manager
option), you’ll
end up deleting the directory named foo in your
current directory.[4] As a user, then,
it’s crucial that you understand which security manager is in
place when you run a Java program so that you understand just what
types of operations you are protected against.
The
security manager is a partnership between the Java API and the
implementor of a specific Java application or of a specific
Java-enabled browser. There is a class in the Java API called
SecurityManager
(java.lang.SecurityManager
) which is the
linchpin of this partnership -- it provides the interface that the
rest of the Java API uses to check whether particular operations
are to be permitted. The essential algorithm the Java API uses to
perform a potentially dangerous operation is always the same:
The programmer makes a request of the Java API to perform an operation.
The Java API asks the security manager if such an operation is allowable.
If the security manager does not want to permit the operation, it throws an exception back to the Java API, which in turn throws it back to the user.
Otherwise, the Java API completes the operation and returns normally.
Let’s trace this idea with an example:
package javasec.samples.ch04; import java.io.*; public class Cat { public static void main(String args[]) { try { String s; FileReader fr = new FileReader(args[0]); BufferedReader br = new BufferedReader(fr); while ((s = br.readLine( )) != null) System.out.println(s); } catch (Exception e) { System.out.println(e); } } }
The FileReader
object will in turn create a
FileInputStream
object, and constructing the
input stream
is the first step of the algorithm. When the input stream is
constructed, the Java API performs code similar to this:
public FileInputStream(String name) throws FileNotFoundException { SecurityManager security = System.getSecurityManager( ); if (security != null) { security.checkRead(name); } try { open(name); // open( ) is a private method of this class } catch (IOException e) { throw new FileNotFoundException(name); } }
This is step two of our algorithm and is the essence of the idea
behind the security manager: when the Java API wants to perform an
operation, it first checks with the security manager and then calls a
private method (the open( )
method in this case)
that actually performs the operation.
Meanwhile, the security manager code is responsible for deciding whether or not the file in question should be allowed to be read and, if not, for throwing a security exception:
public class SecurityManagerImpl extends SecurityManager { public void checkRead(String s) { if (theFileIsNotAllowedToBeRead) throw new SecurityException("Not allowed to read " + s); } }
The
SecurityException
class is a subclass of the
RuntimeException
class. Remember that runtime
exceptions are somewhat different than other exceptions in Java in
that they do not have to be caught in the code -- which is why the
checkRead( )
method does not have to declare
that it throws that exception, and why the
FileInputStream
constructor does not have to
catch it. So if the security exception is thrown by the
checkRead( )
method, the
FileInputStream
constructor will return before
it calls the open( )
method -- which is
simply to say that the input file will never be opened because the
security manager prevented that code from being executed.
Typically, the security exception propagates up through all the methods in the thread that made the call; eventually, the top-most method receives the exception, which causes that thread to exit. When the thread exits in this way, it prints out the exception and the stack trace of methods that led it to receive the exception. This leads to the messages that you’ve probably seen in your Java console:
sun.applet.AppletSecurityException: checkread at sun.applet.AppletSecurity.checkRead(AppletSecurity.java:427) at java.io.FileOutputStream.<init>(FileOutputStream.java) at Cat.init(Cat.java:7) at sun.applet.AppletPanel.run(AppletPanel.java:273) at java.lang.Thread.run(Thread.java)
If the security exception is not thrown -- that is, if the security manager decides that the particular operation should be allowed -- then the method in the security manager simply returns and everything proceeds as expected.
Several methods in the SecurityManager
class are
similar to the checkRead( )
method. It is up to
the Java API to call those methods at the appropriate time. You may
want to call those methods from your own Java code (using the
technique shown in the previous example), but that’s never
required. Since the Java API provides the interface to the virtual
operating system for the Java program, it’s possible to isolate
all the necessary security checks within the Java API itself.
One exception to this guideline occurs when you extend the virtual
operating system of the Java API, and it is important to ensure that
your extensions are well-integrated into Java’s security
scheme. Certain parts of the Java API -- the
Toolkit
class, the Provider
class, the Socket
class, and others -- are
written in such a way that they allow you to provide your own
implementation of those classes. If you’re providing your own
implementation of any one of these classes, you have to make sure
that it calls the security manager at appropriate times.
It’s important to note that there is (by design) no attempt in
the Java API to maintain any sort of state. Whenever the Java API
needs to perform an operation, it checks with the security manager to
see if the operation is to be allowed -- even if that same
operation has been permitted by the security manager before. This is
because the context of the operation is often significant -- the
security manager might allow a FileOutputStream
object to be opened in some cases (e.g., by certain classes) while it
might deny it in other cases. The Java API cannot keep track of this
contextual information, so it asks the security manager for
permission to perform every
operation.
Get Java Security, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.