• No results found

All Permissions

6.3 Implementing a Class Loader

Now we'll look at how to implement a class loader. The class loader we implement will be able to extend the normal permissions that are granted via policy files, and it will enforce certain optional security features of the class loader.

6.3.1 Class Loader Classes

The basic class that defines a class loader is the ClassLoader class (java.lang.ClassLoader):

public abstract class ClassLoader

Turn a series of Java bytecodes into a class definition. This class does not define how the bytecodes are obtained but provides all other functionality needed to create the class definition.

However, the preferred class to use as the basis of a class loader is the SecureClassLoader class (java.security.SecureClassLoader):

public class SecureClassLoader extends ClassLoader

Turn a series of Java bytecodes into a class definition. This class adds secure functionality to the ClassLoader class, but it still does not define how bytecodes are obtained. Although this class is not abstract, you must subclass it in order to use it.

The secure class loader provides additional functionality in dealing with code sources and protection domains.

You should always use this class as the basis of any class loader you work with; in fact, the ClassLoader class would be private were it not for historical reasons.

There is a third class in this category: the URLClassLoader class (java.net.URLClassLoader):

public class URLClassLoader extends SecureClassLoader

Load classes securely by obtaining the bytecodes from a set of given URLs.

If you're loading classes through the filesystem or from an HTTP server, then the URLClassLoader provides a complete definition of a class loader. In addition, you can override some of its methods if you want to modify the security policy of classes that it defines.

6.3.2 Key Methods of the Class Loader

The ClassLoader class and its subclasses have three key methods that you work with when creating your own class loader.

6.3.2.1 The loadClass( ) method

The loadClass( ) method is the only public entry into the class loader:

public Class loadClass(String name)

Load the named class. A ClassNotFoundException is thrown if the class cannot be found.

This is the simplest way to use a class loader directly: it requires that the class loader be instantiated and then be used via the loadClass( ) method. Once the Class object has been constructed, there are three ways in which a method in the class can be executed:

A static method of the class can be executed. This is the technique the Java virtual machine uses to execute the main( ) method of a Java application once the initial class has been loaded, but this is not generally a technique used by Java applications.

An object of the class can be constructed using the newInstance( ) method of the Class class, but only if the class has an accessible constructor that requires no arguments. Once the object has been constructed, methods with well−known signatures can be executed on the object. This is the technique that a program like appletviewer uses: it loads the initial class of the applet, constructs an instance of the applet (which calls the applet's no−argument constructor), and then calls the applet's init( ) method (among other methods). It is also used by factory classes throughout Java.

The reflection API can be used to call a static method on the class or to construct instances of the object and execute methods on that object. The reflection API allows more flexibility than the second choice since it allows arguments to be passed to the constructor of the object.

In addition, every time that a class needs the definition of any other class, it calls the loadClass( ) method of its class loader.

The correct implementation of the loadClass( ) method is crucial to the security of the virtual machine.

For instance, one operation this method performs is to call the parent class loader to see if it has already defined a particular class; this allows all the core Java classes to be loaded by the primordial class loader. If that operation is not performed correctly, security could suffer. As a developer you should be careful when you override this method; as an administrator, this is one of the reasons to prevent untrusted code from creating a class loader.

6.3.2.2 The findClass( ) method

The loadClass( ) method performs a lot of setup and bookkeeping related to defining a class, but from a developer perspective, the bulk of the work in creating a Class class object is performed by the

findClass( ) method:

protected Class findClass(String name)

Load the class specified in the name parameter. The name will be the fully−qualified package name of the class (e.g., java.lang.String).

The findClass( ) method uses whatever mechanism it deems appropriate to load the class (e.g., by reading a class file from the file system or from an HTTP server). It is then responsible for creating the protection domain associated with the class and using the next method to create the Class class object.

6.3.2.3 The defineClass( ) methods

These methods all take an array of Java bytecodes and some information that specifies the permissions associated with the class represented by those bytecodes. They all return the Class class object:

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

protected final Class defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError protected final Class defineClass(String name, byte[] b, int off, int len, CodeSource cs) throws ClassFormatError

Create a class based on the bytecodes in the given array. The protection domain associated with the class varies based on which form is used:

In the first method, the class is assigned to the default protection domain.

In the second method, the class is assigned to the given protection domain.

In the third method, the protection domain is defined by the class loader based on the given code source.

The third method in this list is available only within the SecureClassLoader class and its subclasses (including the URLClassLoader class). When you use that method, the class loader will ask what permissions are associated with a particular code source by calling this method:

protected PermissionCollection getPermissions(CodeSource cs)

Return the permissions that should be associated with the given code source. The default implementation of this method calls the getPermissions( ) method of the Policy class.

Note that this gives you two effective ways in which to override the policy set up by policy files: by supplying your own Policy class or by supplying your own secure class loader that overrides this method.

6.3.3 Responsibilities of the Class Loader

When you implement a class loader, you override some or all of the methods we've just listed. In sum, the class loader must perform the following steps:

The security manager is consulted to see if this program is allowed to access the class in question. If it is not, a security exception is thrown. This step is optional; it should be implemented at the beginning of the loadClass( ) method. This corresponds to the use of the accessClassInPackage permission.

1.

If the class loader has already loaded this class, it finds the previously defined class object and returns that object. This step is built into the loadClass( ) method.

2.

Otherwise, the class loader consults its parent to see if the parent knows how to load the class. This is a recursive operation, so the system class loader will always be asked first to load a class. This prevents programs from providing alternate definitions of classes in the core API (but a clever class loader can defeat that protection). This step is built into the loadClass( ) method.

3.

The security manager is consulted to see if this program is allowed to create the class in question. If it is not, a security exception is thrown. This step is optional; if implemented, it should appear at the 4.

beginning of the findClass( ) method. Note that this step should take place after the parent class loader is queried rather than at the beginning of the operation (as is done with the access check). No Sun−supplied class loader implements this step; it corresponds to the defineClassInPackage permission.

The class file is read into an array of bytes. The mechanism by which the class loader reads the file and creates the byte array will vary depending on the class loader (which, after all, is one of the points of having different class loaders). This occurs in the findClass( ) method.

5.

The appropriate protection domain is created for the class. This can come from the default security model (i.e., from the policy files), and it can be augmented (or even replaced) by the class loader.

Alternately, you can create a code source object and defer definition of the protection domain. This occurs in the findClass( ) method.

6.

Within the findClass( ) method, a Class object is constructed from the bytecodes by calling the defineClass( ) method. If you used a code source in step 6, the getPermissions( ) method will be called to find the permissions associated with the code source. The defineClass(

) method also ensures that the bytecodes are run through the bytecode verifier.

7.

Before the class can be used, it must be resolved −− which is to say that any classes that it immediately references must also be found by this class loader. The set of classes that are

immediately referenced contains any classes that the class extends as well as any classes used by the static initializers of the class. Note that classes that are used only as instance variables, method parameters, or local variables are not normally loaded in this phase: they are loaded when the class actually references them (although certain compiler optimizations may require that these classes be loaded when the class is resolved). This step happens in the loadClass( ) method.

8.

In the next two sections, we'll see how this plays out in each of the class loader types. Note that we do not show how to subclass the ClassLoader class directly: all class loaders should subclass the

SecureClassLoader class or its subclasses instead.

6.3.4 Using the URL Class Loader

If you want to use a custom class loader, the easiest route is to use the URL class loader. This limits the number of methods that you have to override.

To construct an instance of this class, use one of the following constructors:

public URLClassLoader(URL urls[])

public URLClassLoader(URL urls[], ClassLoader parent)

Construct a class loader based on the given array of URLs. This class loader attempts to find a class by searching each URL in the order in which it appears in the array.

The parent of this class loader will be the class loader passed to the constructor or, if one is not provided, the class loader of the class that is creating the URLClassLoader object.

An instance of the URLClassLoader class may also be obtained via one of these methods:

public static URLClassLoader newInstance(URL[] urls)

public static URLClassLoader newInstance(URL[] urls, ClassLoader parent)

Create and return a URL class loader. The difference between these methods and constructing a URL class loader directly is that the class loader returned from these methods will call the security

manager's checkPackageAccess( ) method before it attempts to define a class. Only class loaders obtained this way will perform that optional step (unless you write your own class loader to perform that step).

So a URL class loader that you construct directly will not implement step 1 in the list above, while one obtained from the newInstance( ) method will. Neither implementation provides step 4 (calling the checkPackageDefinition( ) method of the security manager).

We can construct a URL class loader like this:

URL urls[] = new URL[2];

urls[0] = new URL("http://piccolo.East/~sdo/");

urls[1] = new URL("file:/home/classes/LocalClasses.jar");

ClassLoader parent = this.getClass().getClassLoader( );

URLClassLoader ucl = new URLClassLoader(urls, parent);

When we use this class loader to load the class com.sdo.Car, the class loader first attempts to load it via http://piccolo.East/~sdo/com/sdo/Car.class; if that fails, it looks for the class in the LocalClasses.jar file.

This class loader is the basis of the class loader used by the command−line interpreter. In that case, the array of URLs is created based on the list of URLs that make up the classpath.

To implement a URL class loader, we follow the steps listed before.

6.3.4.1 Step 1: Optionally call the checkPackageAccess( ) method

If you need to modify other behavior of the URL class loader, then you cannot use the newInstance( ) method. In that case, in order to use the checkPackageAccess( ) method, you must override the loadClass( ) method like this:

public final synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

// First check if we have permission to access the package.

SecurityManager sm = System.getSecurityManager( );

if (sm != null) {

int i = name.lastIndexOf('.');

if (i != −1) {

sm.checkPackageAccess(name.substring(0, i));

} }

return super.loadClass(name, resolve);

}

6.3.4.2 Step 2: Use the previously−defined class, if available

The loadClass( ) method of the ClassLoader class performs this operation for you, which is why we've called the super.loadClass( ) method.

6.3.4.3 Step 3: Defer class loading to the parent

The loadClass( ) method of the ClassLoader class performs this operation.

6.3.4.4 Step 4: Optionally call the checkPackageDefinition( ) method

In order to call the checkPackageDefinition( ) method, you must override the findClass( ) method:

protected Class findClass(final String name) throws ClassNotFoundException {

// First check if we have permission to access the package.

SecurityManager sm = System.getSecurityManager( );

if (sm != null) {

int i = name.lastIndexOf('.');

if (i != −1) {

sm.checkPackageDefinition(name.substring(0, i));

} }

return super.findClass(name);

}

6.3.4.5 Step 5: Read in the class bytes

The URL class loader performs this operation for you by consulting the URLs that were passed to its constructor. If you need to adjust the way in which the class bytes are read, you should use the SecureClassLoader class instead.

6.3.4.6 Step 6: Create the appropriate protection domain

The URL class loader will create a code source for each class based on the URL from which the class was loaded and the signers (if any) of the class. The permissions associated with this code source will be obtained by using the getPermissions( ) method of the Policy class, which by default will return the

permissions read in from the active policy files. In addition, the URL class loader will add additional permissions to that set:

If the URL has a file protocol, it must specify a file permission that allows all files that descend from the URL path to be read. For example, if the URL is file:///xyz/classes/, then a file permission with a name of /xyz/classes/− and an action list of read will be added to the set of permissions. If the URL is a jar file (file:///xyz/MyApp.jar), the name file permission will be the URL itself.

If the URL has an HTTP protocol, then a socket permission to connect to or accept from the host will be added. For example, if the URL is http://piccolo/classes/, then a socket permission with a name of piccolo:1− and an action list of connect,accept will be added.

If you want to associate different permissions with the class, then you should override the

getPermissions( ) method. For example, if we wanted the above rules to apply and also allow the class to exit the virtual machine, we'd use this code:

protected PermissionCollection getPermissions(CodeSource codesource) { PermissionCollection pc = super.getPermissions(codesource);

pc.add(new RuntimePermission("exitVM"));

return pc;

}

We could completely change the permissions associated with the class (bypassing the Policy class altogether) by constructing a new permission collection in this method rather than calling

super.getPermissions( ). The URL class loader will use whatever permissions are returned from this getPermissions( ) method to define the protection domain that will be associated with the class.

6.3.4.7 Steps 7−8: Define the class, verify it, and resolve it

These steps are handled internally by theURL class loader.

6.3.5 Using the SecureClassLoader Class

If you need to load bytes from a source that is not a URL (or from a URL for which you don't have a protocol handler, like FTP), then you'll need to extend the SecureClassLoader class. A subclass is required because the constructors of this class are protected, and in any case you need to override the findClass( )

method in order to load the class bytes.

The subclass can call either of these constructors:

protected SecureClassLoader( )

protected SecureClassLoader(ClassLoader parent)

Create a secure class loader. The parent argument will become the parent of this class loader; if none is provided, then the class loader that calls the constructor will be the parent class loader.

The steps to use this class are exactly like the steps for the URLClassLoader class, except for step 5. To implement step 5, you must override the findClass( ) method like this:

protected Class findClass(final String name) throws ClassNotFoundException { // First check if we have permission to access the package.

// You could remove these 7 lines to skip the optional step 4.

SecurityManager sm = System.getSecurityManager( );

if (sm != null) {

} catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException( );

} }

The syntax of this method is complicated by the fact that we need to load the class bytes in a privileged block.

Depending on your circumstances, that isn't strictly necessary, but it's by far the most common case for class loaders. Say that your class loader loads class A from the database; that class is given minimal permissions.

When that class references class B, the class loader will be asked to load class B and class A will be on the stack. When it's time to load the new class bytes, we need to load them with the permissions of the class loader rather than the entire stack, which is why we use a privileged block.

Notwithstanding, the try block has three operations: it loads the class bytes, it defines a code source for that class, and it calls the defineClass( ) method to create the class. The first two of the operations are encapsulated in the readClassBytes( ) and getCodeSource( ) methods; these are methods that

you must implement.

Loading the class bytes is an operation left to the reader. The reason for providing your own class loader is that you want to read the class bytes in some special way; otherwise, you'd use the URLClassLoader class.

The code source is another matter: we must determine a URL and a set of certificates that should be associated with the class.

In a signed jar file, the certificates are read from the jar file and the URL is the location of the jar file. In Chapter 12, we'll show how to get the certificates from a standard jar file and construct the appropriate code source. If your class definition isn't coming from a URL, then you must be a little creative. The simplest approach is to create an arbitrary URL. You could also use the methods we examine in Chapter 10, and load one or more certificates from the keystore; you would then use both items to construct the code source.

The defineClass( ) method will call back to the getPermissions( ) method in order to complete the definition of the protection domain for this class. And that's why the URL used to construct the code source can be arbitrary: when you write the getPermissions( ) method, just make sure that you understand what the URL actually is. In default usage, the URL would be used to find entries in the policy files, but since you're defining your own permissions anyway, the contents of the URL don't matter. What matters is that you follow a consistent convention between the definition of your getCodeSource( ) and findClass( ) methods.

Hence, possible implementations of the getPermissions( ) and getCodeSource( ) methods are as

Hence, possible implementations of the getPermissions( ) and getCodeSource( ) methods are as