We'll now turn our attention to implementing security policies. Our goal is to show how to write a security manager--one that can be used in conjunction with the access controller, and one that can stand alone. We'll plug these security managers into our JavaRunner program, and we'll also discuss the implementation of the security manager that comes with the Launcher and how that security manager may be installed.
In order to make our implementation of the security manger a bit easier, we'll provide a few utility classes.
As we intimated above, there are many times when we want to reject an operation if there is any untrusted class on the stack. In order to simplify this operation, we define this method:
private void checkClassLoader(String ask, String ex) { // Use the ask string to prompt the user if the operation // should succeed if (inClassLoader()) { throw new SecurityException(ex); } }
We've passed a string to this method that allows us to ask the user if the operation in question should be permitted; for example, the application could pop up a dialog window and give the user the opportunity to accept the operation. Whether or not that ability is a good idea is open to debate; we've left it to the reader to provide the logic to implement that feature (if desired).
There are a number of tests we want our security manager to reject if they are attempted directly by an untrusted class, but should succeed if they are attempted indirectly by an untrusted class. For these tests in Java 1.1, we have to rely on the class depth to tell us whether the call originated from an untrusted class or not. We use this method to help us with that task:
private void checkClassDepth(int depth, String ask, String ex) { int clDepth = classLoaderDepth(); if (clDepth > 0 && clDepth <= depth + 1) { throw new SecurityException(ex); } }
Note that we have to add 1 to the class depth for this method to succeed, since calling this method has pushed another method frame onto the stack.
Regardless of the release on which your security manager is based, you typically must write the necessary methods to handle network access, because the default methods of the security manager are usually inadequate. In 1.1, the default behavior for the checkConnect() method is to throw a security violation.
In 1.2, the default behavior for the checkConnect() method is to use the access controller to see if the appropriate entry is in the policy file. This is very useful in some circumstances: we can, for example, specify that all code loaded from network.xyz.com can access any other machine in the xyz.com domain, but no other machines. But we cannot set up a general rule for the mode of network access we're most accustomed to. We cannot set up a rule saying that code loaded from a particular machine can only make a network connection back to that machine. The problem lies in the fact that we cannot pattern match entries in the policy file; we cannot say something like:
grant codeBase http://%template/ { permission java.net.SocketPermission "%template", "connect"; };
So if we want to implement a security policy where code can only make a connection back to the host from which it was loaded, we must provide a new implementation of the checkConnect() method:
private ClassLoader getNonSystemClassLoader() { Class c[] = getClassContext(); ClassLoader sys = ClassLoader.getSystemClassLoader(); for (int i = 1; i < c.length; i++) { ClassLoader cl = c[i].getClassLoader(); if (cl != null && !cl.equals(sys)) return cl: } return null } public void checkConnect(String host, int port) { try { super.checkConnect(host, port); return; } catch (AccessControlException ace) { // continue } //In 1.1, use currentClassLoader() instead ClassLoader loader = getNonSystemClassLoader(); String remoteHost; if (loader == null) return; if (!(loader instanceof JavaRunnerLoader)) throw new SecurityException("Class loader out of sync"); JavaRunnerLoader cl = (JavaRunnerLoader) loader; remoteHost = cl.getHost(); if (host.equals(remoteHost)) return; try { class testHost implements PrivilegedExceptionAction { String local, remote; testHost(String local, String remote) { this.local = local; this.remote = remote; } public Object run() throws UnknownHostException { InetAddress hostAddr = InetAddress.getByname(local); InetAddress remoteAddr = InetAddress.getByName(remote); if (hostAddr.equals(remoteAddr)) return new Boolean("true"); return new Boolean("false"); } } testHost th = new testHost(host, remoteHost); Boolean b = (Boolean) AccessController.doPrivileged(th); if (b.booleanValue()) return; } catch (PrivilegedActionException pae) { //Must be an UnknownHostException; continue and throw exception } throw new SecurityException( "Can't connect from " + remoteHost + " to " + host); }
First, we check our superclass to see if it allows the connection. This is only appropriate for 1.2-based security managers--calling the superclass checks the policy file to see if the connection should be made according to information in that file. If that's true, then we simply want to return: the check should succeed. Otherwise, we continue so we can make sure the destination machine is the same machine we loaded this particular class from. For 1.1, this test must be omitted; the superclass in 1.1 would immediately throw an exception.
If there is no class loader on the stack, we want to permit access to any host, so we simply return. Otherwise, we obtain the hostname the untrusted class was loaded from (via the getHost() method of the class loader) and compare that to the hostname the untrusted class is attempting to contact. If the strings are equal, we're all set and can return. Otherwise, we implement the logic we described earlier by obtaining the IP address for each hostname and comparing the two IP addresses.
Note that the logic here for allowing the InetAddress class to resolve the hostname to an IP address is based on the access controller. For a 1.1-based security manager, you would set the inCheck variable to true, execute the calls that are in the run() method of the testHost class, and then set inCheck to false. You would also need to synchronize this method and the getInCheck() methods.
This implementation requires yet another change to the class loader we're using. The class loader must now be able to provide us with the name of the host from which a particular class was loaded. Since our class loader is based on a URL, that's an easy method to implement: we simply return the host of the URL:
public class JavaRunnerLoader extends SecureClassLoader { URL urlBase; ... other code from previous examples ... String getHost() { return urlBase.getHost(); } }
If you choose to implement a different network security model for your checkConnect() method, there are a few things that you should be aware of:
The checkConnect() method is frequently called with a port of -1. That usage comes primarily from the methods of the InetAddress class; in order to resolve the name of a machine, you must be able to make a connection to that machine. So if you want to restrict a connection to the privileged ports on your machine (those less than 1024), make sure you test to see that the port is between and 1023, rather than simply less than 1024.
The host argument passed to the checkConnect() method is frequently an IP address rather than a symbolic hostname. This is an artifact of the way in which the default socket implementation (that is, the PlainSocketImpl class) operates: this class actually generates two calls to the checkConnect() method. The first call contains the actual hostname and a port number of -1 (because the PlainSocketImpl class has called the InetAddress.getByName() method), and the second call contains the IP address and the actual port number.
If you choose to disallow all network access by untrusted classes and you are using a network-based class loader to load classes, you cannot simply write a checkConnect() method that calls the inClassLoader() method and throws an exception if it returns true. The class loader must be allowed to open a socket in order to retrieve additional classes that are referenced by the untrusted class, and such a request will contain the untrusted class on the stack when the call is made. In Java 1.1, you can use the inClass() method to see if the class loader is attempting to open the socket, in which case you should let the operation succeed. In Java 1.2, you can use the doPrivileged() method of the access controller from within the class loader to attempt to open the URL.
There is another checkConnect() method that accepts as arguments the hostname, the port number, and an arbitrary object (a context). Like the similar checkRead() method, this version of the checkConnect() method is never called by the Java API, so the easiest route to take is not to implement it at all. The type of information you might choose to encode within the context could be, for example, the hostname that was retrieved from the current class loader. However, since the security manager is responsible for obtaining the context in the first place, there's no reason why that information cannot be used directly rather than calling this second checkConnect() method.
You may want to implement a similar policy in the checkAccept() method so that a class can only accept a connection from the host from which it was loaded. Since we've just implemented that logic in the checkConnect() method, the easiest way to implement this method is:
public void checkAccept(String host, int port) { try { super.checkAccept(host, port); return; } catch (AccessControlException ace) { // continue } checkConnect(host, port); }
In Java 1.2, there is another way to achieve the network permissions we just outlined. Instead of overriding the checkConnect() method of the security manager, we can arrange for the protection domain of each class to carry with it the permission to open a socket to the host it was loaded from. We can add this permission without regard to the permissions that might be in the policy file.
This implementation requires us to override the getPermissions() method of the SecureClassLoader class as follows:
protected PermissionCollection getPermissions(CodeSource cs) { if (!cs.equals(this.cs)) return null; Policy pol = Policy.getPolicy(); PermissionCollection pc = pol.getPermissions(cs); pc.add(new SocketPermission(urlBase.getHost(), "connect"); return pc; }
As long as we use the correct code source to define the class, when the class loader resolves its permissions the appropriate socket permission will be added to the user-defined set of permissions.
Implementing a model of thread security requires that you implement the checkAccess() methods as well as implementing the getThreadGroup() method. In 1.1, the checkAccess() methods by default throw a security exception. In 1.2, the default behavior of the security manager is to allow the checkAccess() method to succeed unless the target thread is a member of the system thread group or the target thread group is the system thread group. In those cases, the program must have been granted a runtime permission of modifyThread or modifyThreadGroup (depending on which checkAccess() method is involved) for the operation to succeed. Hence, any thread can modify any thread or thread group except for those belonging to the system thread group. Both releases return the thread group of the calling thread for the getThreadGroup() method.
We'll show an example that implements a hierarchical notion of thread permissions which fits well within the notion of the virtual machine's thread hierarchy (see Figure 6-1). In this model, a thread can manipulate any thread that appears within its thread group or within a thread group that is descended from its thread group. In the example, Program #1 has created two thread groups. The Calc thread can manipulate itself, the I/O thread, and any thread in the Program #1 thread groups; it cannot manipulate any threads in the system thread group or in Program #2's thread group. Similarly, threads within Program #1's thread subgroup #1 can only manipulate threads within that group.
This is a different security model than that which is implemented by the JDK's appletviewer and by some browsers in 1.1. In those models, any thread in any thread group of the applet can modify any other thread in any other thread group of the applet, but threads in one applet are still prevented from modifying threads in another applet or from modifying the system threads. But the model we'll describe fits the thread hierarchy a little better.
Note that this security model doesn't fit well within the idea of thread permissions and protection domains. An entry in the policy file granting permission to manipulate threads to the classes from which Program #1 is loaded will thus grant Program #1 permission to manipulate any threads in the virtual machine. The 1.2 default security manager checks for the modifyThread and modifyThreadGroup permissions as described above.
The key to our model of thread security depends on the getThreadGroup() method. We can use this method to ensure that each class loader creates its threads in a new thread group as follows:
If the program attempts to create a thread in a particular thread group, the checkAccess() method can throw a security exception if the thread group in question is not a descendant of the thread group that belongs to the class loader.
If the program attempts to create a thread without specifying the thread group to which it should belong, we can arrange for the getThreadGroup() method to return the class loader's default thread group. This works because the constructors of the thread class call the getThreadGroup() method directly to obtain the thread group to which a thread should belong.
The simplest way to implement getThreadGroup() is to create a new thread group for each instance of a class loader. In a browser-type program, this does not necessarily create a new thread group for each applet, because the same instance of a class loader might load two or more different applets if those applets share the same codebase. If we adopt this approach, those different applets will share the same default thread group. This might be considered a feature. It is also the approach we'll show; the necessary code to put different programs loaded by the same class loader into different thread groups is a straightforward extension.
Our getThreadGroup() method, then, looks like this:
public ThreadGroup getThreadGroup() { ClassLoader loader = currentClassLoader(); if (loader == null || !(loader instanceof JavaRunnerLoader)) return super.getThreadGroup(); JavaRunnerLoader cl = (JavaRunnerLoader) loader; return cl.getThreadGroup(); }
We want each instance of a class loader to provide a different thread group. The simplest way to implement this logic is to defer to the class loader to provide the thread group. If there is no class loader, we'll use the thread group our superclass recommends (which, if we've directly extended the SecurityManager class, will be the thread group of the calling thread).
Of course, not every class loader has a getThreadGroup() method, so if the class loader we find isn't of the class that we expect, we again have to defer to our superclass to provide the correct thread group (which, by default, is the thread group of the calling thread). Otherwise, we can ask the class loader, which implies that we need to provide a getThreadGroup() method within that class loader:
public class JavaRunnerLoader extends SecureClassLoader { private ThreadGroup threadGroup; private static int groupNum; ... ThreadGroup getThreadGroup() { if (threadGroup == null) threadGroup = new ThreadGroup("JavaRunner ThreadGroup-" + groupNum++); return threadGroup; } }
Now we've achieved the first part of our goal: when the program attempts to create a thread without specifying a thread group that it should belong to, the thread is assigned to the desired group. For the second part of our goal, we need to ensure that the checkAccess() method only allows classes from that class loader to create a thread within that thread group (or one of its descendent thread groups).
In order to achieve this second goal, we must implement the checkAccess() methods as follows:
public void checkAccess(Thread t) { ThreadGroup current = Thread.currentThread().getThreadGroup(); if (!current.parentOf(t.getThreadGroup())) super.checkAccess(t); } public void checkAccess(ThreadGroup tg) { ThreadGroup current = Thread.currentThread().getThreadGroup(); if (!current.parentOf(tg)) super.checkAccess(tg); }
This logic prevents threads in sibling thread groups from manipulating each other, as well as preventing threads in groups that are lower in the thread hierarchy from manipulating threads in their parent groups. Though that makes it more restrictive than the model employed by the 1.1 JDK, it matches the concept of a thread group hierarchy better than the JDK's model.
There are three caveats with this model. The first has to do with the way in which thread groups are created. When you create a thread group without specifying a parent thread group, the new thread group is placed into the thread hierarchy as a child of the thread group of the currently executing thread. For example, in Figure 6-1, when the Calc thread creates a new thread group, by default that thread group is a child of Program Thread Group #1 (e.g., it could be Program Subgroup #1). Hence, if you start a program, you must ensure that it starts by executing it in the thread group that would be returned by its class loader--that is, the default thread group of the program. That's why we included that logic at the beginning of our JavaRunner example.
The second caveat is that threads may not be expecting this type of enforcement of the thread hierarchy, since it does not match many popular browser implementations. Hence, programs may fail under this model, while they may succeed under a different model.
Finally, remember that in 1.2, the stop() method of the Thread class first calls the checkPermission() class of the security manager to see if the current stack has a runtime permission of "stopThread". For backward compatibility, all protection domains have that permission by default, but a particular user may change that in the policy file.
A final area for which the default security manager is sometimes inadequate is the manner in which it checks for package access and definition. In 1.1, the default security manager rejects all package access and definition attempts.
In 1.2, the situation is complex. For package access, the security manager looks for a property defined in the java.security file named package.access. This property is a list of comma-separated package names for which access should be checked. If the class loader uses the checkPackageAccess() method (many do not) and attempts to access a package in the list specified in the java.security file, then the program must have a runtime permission with a name of accessClassInPackage.<packagename>. For defining a class, the operation is similar; the property name in the java.security file is package.definition, and the appropriate runtime permission has a name of defineClassInPackage.<packagename>. This model works well, but it requires that the java.security file and all the java.policy files be coordinated in their attempts to protect package access and definition.
For that reason, and also to provide a better migration between releases (and because it's the only way to do it in 1.1), you may want to include the logic to process some policies within your new security manager. In that way, users will not need to make any changes on their system; in this case, the user will not have to put the appropriate RuntimePermission entries into the java.policy files by hand.
The checkPackageAccess() method is most often used to restrict untrusted classes from directly calling certain packages--e.g., you may not want untrusted classes directly calling the com.xyz.support pacakge of your application. Unfortunately, the only way to do that while relying on the security manager is to rely on the class depth, which we want to avoid.
One solution is to introduce a property for the application that defines packages that the untrusted classes in the application are not allowed to access. HotJava and the appletviewer do this by setting properties of the form:
package.restrict.access.pkgname = true
In the checkPackageAccess() method, you can use the parameter to construct this property (substituting for the pkgname) and see if the corresponding property is set: if it is, and if the inClassLoader() method returns true, you can throw the security exception. For our purposes, however, we will allow classes to access any package, and write our checkPackageAccess() method like this:
public void checkPackageAccess(String pkg) { }
The checkPackageDefinition() method is somewhat different--you probably don't want untrusted classes defining things in the java package, for example. So we want to test for that package explicitly. But we also want to respect the permissions for the applications, so the general solution for cases such as this is to first check with the access controller (via the security manager's superclass), and then to implement the original logic:
public void checkPackageDefinition(String pkg) { if (!pkg.startsWith("java.")) return; try { super.checkPackageDefinition(pkg); return; } catch (AccessControlException ace) { // continue } if (inClassLoader()) throw new SecurityException("Can't define java classes"); }
Note that the name in the test contains the period separator--you don't want an untrusted class to be able to define a class named java.lang.String, but you do want it to be able to define a class named javatest.myClass. On the other hand, you may or may not want to grant access to classes in the javax package. This method also requires a change to the class loader that we'll show at the end of the chapter.
We'll now give specific information on how to establish a security policy for 1.2. In Java 1.2, the SecurityManager class is a concrete class--you use it directly, or you may subclass it. The simplest implementation of the SecurityManager class is:
public class JavaRunnerManager extends SecurityManager { }
The JavaRunnerManager class inherits the default behavior of the SecurityManager class for all its methods--but it's important to realize that this default behavior is not the behavior we discussed in Chapter 4, "The Security Manager Class". The behavior we discussed in that chapter stemmed from the security manager implementations of various popular browsers--that may be the security that is appropriate for your application, but the default behavior for the Security Manager class comes from the java.policy files.
The default behavior of the public methods of the SecurityManager class is to call the access controller with an appropriate permission. For example, the implementation of the checkExit() method is:
public void checkExit(int status) { AccessController.checkPermission(new RuntimePermission("exitVM")); }
This is why the default security policy for the application can be specified via the java.policyfiles. Table 6-3 lists the methods of the security manager and the permission they construct when they call the access controller.
There are five slight exceptions to the rules laid out in Table 6-3:
The checkAccess() methods only check for the given permission if the target thread (group) is in the system thread group.
If the command passed to the checkExec() method is not a fully qualified pathname (that is, if the command will be found by examining the user's PATH variable), the string passed to create the file permission is "<<ALL FILES>>." The domain must have permission to execute all files in the filesystem in this case.
The methods that use a context expect the context to be an instance of the AccessControlContext class. They then call the checkPermission() method of that context, using the same permission that would normally be used in that call (e.g., a file permission with a read action for the checkRead() method). As we mentioned, these methods are never called by the core API. If the context is not an access control context, then a SecurityException will be thrown.
The checkTopLevelWindow() method catches the AccessControlException if it is thrown by the access controller. In this case, it returns false. This method does not (by default) throw an exception.
The checkMemberAccess() method does not call the access controller if the program is inspecting public values (that is, if the which flag is Member.PUBLIC) or if the current class loader is the same class that loaded the target class.
For the most part, it's possible to use the default security manager and the permission mappings we've just identified to support virtually any security policy. But there are certain useful exceptions a security manager will often define:
Network permissions may want to follow the implementation outlined above.
Package access and definition permissions may follow the implementation outlined above.
Exit permissions may be summarily granted to all applications (unless the application is a server that should stick around).
Thread permissions may follow the thread hierarchy rather than the default all-or-nothing policy.
For a complete 1.2-based security manager, then, you typically need to override only the methods involved with these four exceptions. The 1.2-based security manager we'll use for our JavaRunner program looks like this:
public class JavaRunnerManager extends SecurityManager { public void checkConnect(String host, int port) { .. follow implementation given above .. } public void checkPackageAccess(String pkg) { .. follow implementation given above .. } public void checkPackageDefinition(String pkg) { .. follow implementation given above .. } public void checkExit(int status) { } public void checkAccess(Thread t) { .. follow implementation given above .. } public void checkAccess(ThreadGroup tg) { .. follow implementation given above .. } }
Establishing a security policy in 1.1 is done only by ensuring that the correct security manager is in place. In this section, we're going to discuss how a 1.1-based security manager can be implemented.
One of the times a security manager is often used in a Java application is in an RMI server. An RMI server has the capability of loading code from an RMI client located on a remote machine and executing that code on the server--essentially transforming the server (temporarily) into a client.[5] In essence, the security ramifications of using RMI servers are similar to those of an applet, but in reverse: you now want to protect your server machine from the side effects of untrusted code it got from a client.
[5]This used to be called "peer computing," although that term has fallen out of favor. But it's a useful concept: just because one machine has to initiate a request shouldn't mean that the roles of client and server have to be immutable.
In the most common case, you'll want your RMI server to have a simple security model. If the code it's executing was completely loaded from the server, the operation should succeed; if any of the code it's executing was loaded from the client, the operation should fail. Hence, the Java API provides the RMISecurityManager class, which implements just such a policy. In general, the methods of the RMISecurityManager class look like this:
public void checkAccess(Thread t) { if (inClassLoader()) throw new SecurityException("checkAccess"); }
You can check the source code (java.rmi.RMISecurityManager) for exact details; this example is a conflation of code found there.
Hence, in the RMI security manager, all local code is trusted and all remote code is untrusted. There are certain methods of this class that have slightly different implementations, however. Because the RMISecurityManager provides a useful basis for a default implementation of your own security manager, we'll list those exceptions here so you can use the RMISecurityManager class and understand where you're starting out.
An untrusted class can check properties only if a special property is set. If an untrusted class wants to check the property foo.bar, the property foo.bar.stub must be set to true.
An untrusted class can read or write a file if that file is a socket. Note that the untrusted class still cannot create the socket.
An untrusted class can connect a socket only if called from certain internal RMI classes. If you're using the RMISecurityManager class as the basis for a non-RMI application, the untrusted class is not able to make any connections.
An untrusted class can create a separate window, but it will have the warning banner.
An untrusted class can access a package unless the external properties specifically prohibit such access.
An untrusted class can access a package definition unless the external properties specifically prohibit such access.
Neither an untrusted class nor a trusted class can change a socket factory.
An untrusted class can only check the member access for public members.
In Java 1.1, the SecurityManager class is abstract, so you can't directly instantiate a security manager object. However, none of the methods of SecurityManager is itself abstract, meaning that the simplest implementation of the SecurityManager class is this:
public class StrictSecurityManager extends SecurityManager { }
The StrictSecurityManager class inherits the default behavior of the SecurityManager class for all its methods--but once again it's important to realize that this default behavior is not the behavior we discussed earlier in terms of what an untrusted class might or might not be allowed to do. The default behavior of the public methods in the SecurityManager class in 1.1--and hence of the StrictSecurityManager class above--is to deny every operation to every class, trusted or not. Each of the public methods of the SecurityManager class looks similar to this:
public void checkAccess(Thread g) { throw new SecurityException (); }
Thus, if you want to implement your own security manager, you need only override the methods for which you want to provide a more relaxed security policy. If you want to allow (at least some) thread operations, you must override the checkAccess() methods; if you do not override those methods, no thread operations will be allowed by any class.
In typical usage, a 1.1-based security manager might want to deny a large number of operations if there is any untrusted class on the stack. These methods might be implemented with the checkClassLoader() method we discussed above. Candidates for this type of check are:
Class DefinitioncheckAccept() |
Class DefinitioncheckMemberAccess() |
Class DefinitioncheckSecurityAccess() |
Class DefinitioncheckAWTEventQueueAccess() |
Class DefinitioncheckMulticast() |
Class DefinitioncheckSystemClipboardAccess() |
Class DefinitioncheckExit() |
Class DefinitioncheckPrintJobAccess() |
Class Definition |
Class DefinitioncheckListen() |
Class DefinitioncheckSecurityAccess() |
Class Definition |
Similarly, there are a number of tests that we want to fail if they are attempted directly by an untrusted class, but that we want to succeed if they are attempted indirectly by an untrusted class. For these tests, we have to rely on the class depth to tell us whether the call originated from an untrusted class or not; we use the checkClassDepth() method to help us with that task. Here are the candidate methods for this test along with the depth that checked for each method:
Class DefinitioncheckCreateClassLoader() |
2 |
Class DefinitioncheckLink() |
3 |
Class DefinitioncheckDelete() |
2 |
Class DefinitioncheckPropertiesAccess() |
2 |
Class DefinitioncheckExec() |
2 |
Class DefinitioncheckPropertyAccess() |
2 |
Finally, there are some methods we must implement with their own logic. Although we've saved these for last, they are most interesting since these are the methods that you'll need to pay the most attention to when you write your own security manager.
If you are going to implement a security manager, you must determine a policy for reading and writing files and implement it in each of the checkRead() and checkWrite() methods. The logic you put into each method is slightly different.
In the case where these methods take a single string argument, the logic is straightforward: the program is attempting to open a file with the given name, and you should either accept or reject that operation. We'll base our decision on the depth of the class loader. Untrusted classes may not directly open a file for reading or writing, but they may cause that to happen through the Java API:
public void checkRead(String file) { checkClassDepth(2, "Read the file " + file, "Can't read local files"); } public void checkWrite(String file) { checkClassDepth(2, "Write the file " + file, "Can't write local files"); }
In the case where these methods take a FileDescriptor as an argument, the policy is a little harder to define. As far as the Java API is concerned, these methods are only called as a result of calling the Socket.getInputStream() or Socket.getOutputStream() methods--which means that the security manager is really being asked to determine if the socket associated with the given file descriptor should be allowed to be read or written. By this time, the socket has already been created and has made a valid connection to the remote machine, and the security manager has had the opportunity to prohibit that connection at that time.
What type of access, then, would you prohibit when you implement these methods? It partially depends on the types of checks your security manager made when the socket was created. We'll assume for now that a socket created by an untrusted class can only connect to the site from which the class was loaded, while a socket created by a trusted class can connect to any site. Hence, you might want to prohibit an untrusted class from opening the data stream of a socket created by a trusted class--although if the class is trusted, you typically want to trust that class's judgement, and if that class passed the socket reference to an untrusted class, the untrusted class should be able to read from or write to the socket.
On the other hand, it is important to be sure that these methods are actually being called from the socket class. An untrusted class could attempt to pass an arbitrary file descriptor to the File*Stream constructor, breaking into your machine.
Typically, then, the only checks you put into this method are to determine that the FileDescriptor object is valid and the FileDescriptor object does indeed belong to the socket class:
public void checkRead(FileDescriptor fd) { if (!inClassLoader()) return; if (!fd.valid() || !inClass("java.net.SocketInputStream")) throw new SecurityException("Can't read a file descriptor"); } public void checkWrite(FileDescriptor fd) { if (!inClassLoader()) return; if (!fd.valid() || !inClass("java.net.SocketOutputStream")) throw new SecurityException("Can't write a file descriptor"); }
A typical 1.1-based security manager would implement thread, network, and package access as we described above.
There is one more method of the security manager that we must implement with slightly different rules: the checkTopLevelWindow() method. This method uses the standard class depth test for an untrusted class, but it shouldn't throw an exception, so it looks like this:
public boolean checkTopLevelWindow(Object window) { if (classLoaderDepth() == 3) return false; return true; }
Copyright © 2001 O'Reilly & Associates. All rights reserved.