< Day Day Up > |
A class loader is responsible for loading classes from a specific location. At runtime, multiple class loaders may be in effect at any given time. The set of classes loaded by a particular class loader is known as that class loader's name space . To run a Java program, the JVM needs to locate and load into memory the classes comprising that program. In a traditional execution environment, this service is provided by the loader and linker utilities, which load code from the file system in a platform-specific way. In the JRE, things are complicated by the fact that code may be loaded from multiple locations, some of which may be remote, unsecure, or untrusted. Therefore, loading classes into the JVM at runtime has several security implications. 7.2.1 Security Responsibilities of the Class-Loading MechanismClass loaders are the gatekeepers of the JVM, controlling which bytecodes may be loaded and which should be rejected. As such, class loaders have a number of security responsibilities:
The class-loading mechanism has another useful side effect. By controlling how the JVM loads code, all platform-specific file I/O involved in the loading of the classes is channeled through one part of the JVM, thus making porting the JVM to other platforms a much simpler task. 7.2.2 Levels of Trustworthiness of Loaded ClassesJava code can be loaded from various sources. Some of the more common sources, listed from most trusted to least trusted, are
Given that code running on a JVM can come from trusted and untrusted sources, the class-loading mechanism must guarantee
As discussed in Section 7.2.3.2 on page 216, a correct implementation of the class-loading mechanism must enforce isolation for unrelated name spaces. Figure 7.4 shows that the JVM may have many class loaders operating at any time, each of which is responsible for locating and loading classes from different sources. However, in general, these sources can be divided into two categories, trusted and untrusted. 7.2.2.1 Loading Classes from Trusted SourcesThe primordial class loader , which is a built-in part of the JVM, is also known as the internal , or null , or default class loader . The primordial class loader is responsible for loading the trusted classes of the Java runtime. Classes loaded by the primordial class loader are regarded as special insofar as they are not subjected to verification prior to execution. They are assumed to be well- formed , safe Java classes. In addition, they are not subjected to any security policy restrictions. In early Java versions, these classes were the JVM core classes, along with any classes that could be found using the CLASSPATH system environment variable. Obviously, if would-be attackers could somehow introduce a malicious class into the CLASSPATH of a JVM, they could cause serious damage. In the Java 2 platform, this exposure is minimized by removing the core-class path information from the CLASSPATH environment variable and subjecting all but the core classes to verification [3] and the security policy.
The core Java 2 classes are located by using a JVM internal property [4] whose value is called the boot class path . This internal property is formed internally from install information. [5]
Some J2SE and J2EE implementations allow a system administrator to extend the set of the system classes by placing custom classes in a particular directory. [6] Therefore, if a request arrives to load one of the classes from this directory, the primordial class loader will have responsibility for loading it. From a security point of view, two things should be noted. First, if a request arrives to load a class, this directory, as part of the boot class path, is inspected before the extension class path, application class path, and any remote location. Therefore, classes in this directory can potentially replace extension and application classes, as well as classes located in the network. Second, classes in this particular directory are considered fully trusted, just like the system classes. They will not be subjected to security policy restrictions, nor will the class file verifier check them for safety and integrity. It is therefore recommended to use operating system protections to make sure that only trusted users are allowed to create this directory and add files to it.
7.2.2.2 Loading Classes from Untrusted SourcesClasses from untrusted sources include application classes, extension classes, and classes from remote network locations. Application ClassesThe Java 2 platform bounds the scope of implicitly trusted classes to only the Java core classes. User classes are not considered fully trusted and are not loaded by the primordial class loader. At JVM start-up, the application class path information is copied from the CLASSPATH environment variable into a JVM internal property. [7] This property is used to start an instance of java.net.URLClassLoader , an implementation of java.lang.ClassLoader . [8] This instance, called the application class loader , is given a list of URLs generated from CLASSPATH , which it will use to locate and load user classes. The application class loader is also responsible for associating the appropriate Permission s with the loaded class, based on the security configuration defined by the JVM administrator. [9]
Extension ClassesFrom a trust viewpoint, classes of the extension framework fall logically in between the fully trusted core classes, for which no policy Permission entries are required and no integrity and safety verification is performed, and the completely untrusted application classes, for which explicit policy Permission entries are required and integrity and safety verification is performed. This framework allows for the installation of JAR files in a specific extensions directory referenced by a JVM internal property. [10] The value of this property is called the extension class path . [11] A URLClassLoader instance, called the extension class loader , is created at JVM start-up and is responsible for loading installed extensions.
Extension classes are subjected to security policy restrictions as well as safety and integrity verification, just like application classes. However, by default, extension classes are typically granted java.security.AllPermission , corresponding to the right to access all the system resources. Therefore, system administrators should allow only trusted users to add files to the extension class path. System administrators can also decide to restrict the access rights granted to the extensions. Classes from Remote Network LocationsIn a Web environment, classes can be loaded from remote network locations. An additional ClassLoader instance is created to load remote classes from a specific set of URLs. [12] Classes from different URLs may result in multiple ClassLoader s being created to maintain separate name spaces. Therefore, there may be multiple instances of the same ClassLoader class at any given time.
On most networks, including the Internet, there are many Web servers from which classes could be loaded. Nothing prevents two webmasters from having different classes with the same name on their Web sites. Within a name space, duplicate fully qualified class names are prohibited ; in other words, a given instance of a ClassLoader cannot load multiple classes with the same fully qualified name. If we did not have a specific ClassLoader instance for any URL from which classes are loaded, we would very quickly run into problems when loading classes with the same fully qualified name from multiple sites. Moreover, it is essential for the security of the JVM to separate classes from different sites so that the classes cannot inadvertently or deliberately cross-reference each other. Imagine what could happen if a mobile program downloaded from a bank's Web site to manage financial transactions became accessible to another mobile program downloaded from a hacker's Web site. Classes from different sites are separated by preventing cross-visibility between name spaces corresponding to different URLs, as shown in Figure 7.5. Because classes from separate Web sites are loaded into separate name spaces for which there is no cross-visibility, a class loaded from a particular URL ”the bank's Web site ”cannot be accessed by a class loaded from a different URL ”the hacker's Web site. Figure 7.5. Name Space Isolation
Classes loaded from remote network locations are considered fully untrusted. Therefore, they are subjected to the class file verifier's checks for integrity and safety purposes. In addition, by default those classes will run confined in the sandbox, unless the security policy of the Java system in which they are loaded grants them explicit Permission s. The class-loading mechanism is also used in a J2EE environment to achieve isolation. Some J2EE implementations give system administrators the ability to isolate J2EE applications from each other by loading each J2EE application in a different name space. In other implementations, it is possible to isolate each module; this means that each Web or Enterprise JavaBeans module is loaded into its own name space. Additionally, name-space separation allows loading the same J2EE application multiple times, each time in a different name space. 7.2.3 The Class-Loading ProcessIt should now be clear that many types of class loaders can be within a Java environment at any time. In addition, multiple instances of a particular type of ClassLoader may be operating at once. In this section, we look at how all the class loaders cooperate to load classes in a secure manner. First, we consider the problem from a design viewpoint. Then, we show how that design is implemented in the Java 2 platform and how it should be implemented by any Java product needing to develop a customized class-loading mechanism. 7.2.3.1 Enforcing the Correct Search Order: The DesignIn this section, we look at some of the design aspects of the Java 2 class-loading architecture. In other words, we describe what is supposed to happen from the viewpoint of the Java architects . Let us assume that, at a certain time during the execution of a program, class A , which was loaded by class loader x , makes a reference to class B , as shown in point 1 in Figure 7.6. When class B is referenced, the JVM execution environment receives the request (point 2 in Figure 7.6) and invokes class loader x , associated with the requesting program A, to locate and load the referenced class (point 3 in Figure 7.6). Class loader x is associated with A because x was the class loader instance that loaded A 's class. Identifying the ClassLoader instance that loaded A is as simple as invoking the method getClassLoader() on A , as depicted in Figure 7.6. If A was loaded by the primordial class loader, invoking getClassLoader() returns null . Figure 7.6. Class-Loading Process Scenario
Once x has received the request to load B, x becomes responsible for loading B (point 4 in Figure 7.6). First, x checks whether it had already loaded B . Loaded classes are cached, and there is no reason to load them again. If x had already loaded B, x interacts with the SecurityManager to see whether class A has the Permission to access B . If A does not have the Permission , a java.lang.SecurityException is thrown. If, however, A has the right Permission , x returns a reference to the existing Class object. If B has not already been loaded, x invokes the SecurityManager to see whether A has the Permission to create the requested class, B . If it does not, a SecurityException is thrown. Otherwise, x first tries to find the requested class in the core Java API in the boot class path. This step prevents the JVM's core classes from being replaced by classes from another, less trusted location. If class B is found, it is loaded by the primordial class loader into the class area (see Figure 7.1 on page 204), and a reference to the Class object is returned to A . If B is not found in the boot class path, x will try to find it in any JVM extensions located in the extension class path. If class B is found, it is loaded by the extension class loader into the class area, and a reference to the Class object is returned to A . Extensions are less trusted than the core Java classes but more trusted than the local application classes and the classes loaded from remote network locations. The mechanism we have described so far prevents extension classes from replacing the more trusted system classes and from being replaced by classes from less trusted locations. If we have come to this point without finding B, x will look through the application class path before going to the network to locate the class. If it is found in the application class path, the class is loaded by the application class loader, and a reference to the Class object is returned to A . Otherwise, the ClassLoader responsible for finding classes in the network will look for B in the network locations from which it is responsible for loading classes. If this ClassLoader also fails to find the requested class, other user-defined ClassLoader s (see Section 7.2.4 on page 219) may be consulted to see whether they can find it in the other locations from which they are responsible for loading classes: for example, the ROM memory, a database, and so on. If none of the class loaders that received the request can find B, x will throw a java.lang.ClassNotFoundException . If B is found, the class file verifier is responsible for making sure that the class file is well formed (see Section 7.3.1 on page 226). If the bytecode passes verification, the class is loaded into the class area, a Class object is created, and a set of Permission s is associated with the class for subsequent resource authorization checking. B is then linked by resolving any references to other classes within it. This may result in additional calls to B 's class loader to locate and load other classes. Next, static initialization of class B is performed; that is, static variables are defined and static initializers are run. Finally, the class is available to be executed, and a reference to it is returned to A . The steps described in the preceding paragraph are performed regardless of the actual ClassLoader that loaded B . The only exceptional case is if B is one of the system classes in the boot class path, which ultimately implies that B 's class loader is the primordial loader. As system classes are considered fully trusted and safe to run, the primordial loader will not pass B to the class file verifier (see Figure 7.1 on page 204). In addition, given that system classes are implicitly granted full access to all the system resources, the primordial loader will not associate a set of Permission s with B . Except for these two security- related steps, the rest of the process is the same. 7.2.3.2 The Class-Loading Delegation Hierarchy: The ImplementationEvery ClassLoader 's class, being just another Java class itself, is loaded by a class loader. The primordial class loader, which in general is not a Java class, is not loaded by any class loader and is generated at JVM start-up, and its primary function is to load the Java core classes. This forms a runtime parent/child hierarchical tree relationship between class loaders, with the primordial class loader at the root. This relationship is the basis for the delegation model , which is the recommended implementation model for all ClassLoader s. Every ClassLoader instance, on receiving the request to load a Java class, should immediately delegate the request to its parent class loader . By default, when a program instantiates a ClassLoader object, the program's class loader becomes the ClassLoader object's parent. For example, in the J2SE reference implementation, the extension class loader is created at JVM start-up by one of the JVM's internal system programs, whose class loader is the primordial class loader. Therefore, the extension class loader's parent is the primordial class loader. It is also possible to specify the parent ClassLoader explicitly via a parameter to the ClassLoader 's constructor. In this case, the specified ClassLoader is forced to be the parent. For example, at JVM start-up, in the J2SE reference implementation, the same system program that creates the extension class loader also creates the application class loader. Therefore, the primordial class loader should be the application class loader's parent. However, when the application class loader is constructed , the extension class loader is passed as a parameter to the constructor and becomes the application class loader's parent. If a program creates a user-defined ClassLoader x, x 's parent is by default going to be the application class loader. The parent/child hierarchical tree relationship between class loaders in this scenario is shown in Figure 7.7. Figure 7.7. Delegation Hierarchy Scenario
The runtime parent/child relationship between class loaders always has the primordial class loader at the root and forms the basis for the recommended implementation of the class-loading design. Figure 7.8 illustrates the security advantages coming from the delegation model. Figure 7.8. Referencing Classes in the Delegation Hierarchy
Figure 7.8 shows how the class-loading delegation model guarantees the following.
The class-loading delegation model additionally guarantees that programs whose classes are located in name spaces belonging to different branches of the class-loading delegation tree cannot reference each other, as shown in point 1 in Figure 7.9. This prevents cross-visibility and allows isolation between name spaces belonging to different branches of the delegation tree. Classes loaded in such name spaces are totally unaware of each other, as depicted in the scenario of Figure 7.5 on page 214. However, as shown in Figure 7.8, a class C and its instances can directly reference a class D and its instances if C was loaded by a class loader y and D was loaded by any of y 's ancestor class loaders, w . This scenario is also represented in point 2 in Figure 7.9. This implies that though isolated, two classes belonging to different branches of the delegation tree can still theoretically exchange information by setting and getting static state information on a program whose classes have been loaded by a common class loader ancestor. Therefore, developers of core and extension classes, whose class loaders are ancestors for all the other class loaders, should avoid writing classes whose state information can be mutated and subsequently retrieved by application code. Figure 7.9. Name Space Isolation through the Class-Loading Delegation Model
7.2.4 Building a Customized ClassLoaderApplication writers, including JVM implementers and J2EE container providers, may subclass ClassLoader to handle the loading of classes from different sources ”the Internet, an intranet, local storage, or perhaps even from read-only memory (ROM) in an embedded system ”or to implement additional functions that built-in ClassLoader s do not provide. These functions may include security and auditing. Custom ClassLoader s are not a part of the JVM but rather are part of an application running on top of the JVM. The ability to create additional ClassLoader s is a very powerful feature of Java technology and places a heavy responsibility on the ClassLoader implementer. This feature becomes particularly apparent when you realize that user-written ClassLoader s have the choice of following the delegation model or not. They get first choice on whether to load a class. They can even take priority over the primordial class loader. This enables a user-written ClassLoader to replace any of the system classes, including the SecurityManager . A customized ClassLoader could even refuse to pass a loaded class to the class file verifier for integrity and safety verification. In addition, because the ClassLoader is responsible for associating Permission s with each loaded class, a customized ClassLoader could decide to assign full access to all the system resources to any loaded class. This shows the high degree of responsibility that ClassLoader designers and developers have and justifies the reason why, in order to install a new ClassLoader at runtime, programs need to be granted a specific Permission . Starting with the Java 2 platform, the JRE includes a ClassLoader implementation, java.security.SecureClassLoader , which implements the basic security-related requirements of class loading: checking with the SecurityManager , calling the class file verifier, linking the class, and associating it with the Permission s that the Java system administrator granted to the class. Its constructor is protected; SecureClassLoader is meant to be the basis for the development of other class loaders. URLClassLoader is a general-purpose loader included in the core Java 2 API. URLClassLoader , a subclass of SecureClassLoader , has the ability to find and load class files from a list of URLs. URLClassLoader should meet most of the requirements an application may have for loading class files. If not, developers can easily develop their own ClassLoader s by subclassing SecureClassLoader or URLClassLoader , instead of the ClassLoader abstract class. This way, they can benefit from the function and security built into SecureClassLoader . The Java 2 API also includes the java.rmi.server.RMIClassLoader class to support dynamic class loading with Java RMI. The methods in this class are static, so they can be called directly to load individual unsigned class files from a single URL. Once a class file has been loaded, the RMIClassLoader provides the functionality to define a class from the loaded file. The RMIClassLoader 's name is misleading, as this ClassLoader is much more general purpose than its name implies and can be used to simply load class files. The RMIClassLoader can support several protocols, including HTTP and IIOP. With the Java 2 existing class-loading architecture, there is much less reason to implement a customized ClassLoader . Instances of class URLClassLoader can load classes from a list of URLs. The instances can process class files, JAR files, and signed JAR files; can handle associating Permission s with loaded classes; and are programmed to interact with the SecurityManager during class loading to verify that a class has the right to trigger a reference to another class and that core packages are protected. Finally, URLClassLoader instances obey the class-loading delegation model, so they preserve the various levels of class trustworthiness by preventing less trusted classes from replacing more trusted classes with the same fully qualified name. If, after all this, you still have reason to build your own ClassLoader , such as one that performs class-access auditing or works across a network protocol unsupported by the existing ClassLoader s, you can still benefit from subclassing one of the provided classes. For instance, if you are not using HTTP but everything else is the same, you can implement your own ClassLoader based on SecureClassLoader , and model it after URLClassLoader . The example we show in this section is a Java 2 ClassLoader , Audit2ClassLoader , that extends URLClassLoader to provide class-access auditing, a feature that existing ClassLoader s do not regularly implement. It is interesting to note how much less work is required on the developer's part to write a Java 2 ClassLoader . By simply subclassing URLClassLoader , developers can focus on implementing a particular behavior that their new ClassLoader should provide, with the major security features automatically inherited from the superclass. Audit2ClassLoader overrides the loadClass() method in URLClassLoader , simply recording class load requests in a file, whose name is hard-coded as auditclasses.log , and then asks its parent ClassLoader to load the class (Listing 7.1). By using all of URLClassLoader 's function, the code for Audit2ClassLoader is very short, but it offers an elegant example of OO design and implementation. Listing 7.1. Audit2ClassLoader.javaimport java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; /** * This class extends URLClassLoader by providing an * additional class-auditing function. */ public class Audit2ClassLoader extends URLClassLoader { private DataOutputStream auditlog; /** * Public constructor. Calls URLClassLoader's constructor * and opens a file for recording class load messages. * * @param urls an array or URL objects from which this * ClassLoader is responsible for loading classes. */ public Audit2ClassLoader(URL[] urls) { super(urls); try { auditlog = new DataOutputStream( new FileOutputStream("auditclasses.log")); auditlog.writeBytes("Audit Started:\n"); } catch (IOException e) { System.err.println("Audit file not opened " + "properly\n" + e.toString()); } } /** * The method that actually loads a class file. This * method is invoked to load a new class. The steps that it * must carry out are to write a message to a log file and * to call the parent findClass() method to load, verify, * and resolve the class and associate Permissions with it. * * @param name the fully qualified name of the class to load * @throws ClassNotFoundException if the class was * not found */ public Class loadClass(String name) throws ClassNotFoundException { try { auditlog.writeBytes("loading class " + name + "\n"); } catch (IOException ioe) { System.err.println("Could not write to audit file\n" + ioe.toString()); } try { return super.loadClass(name); } catch (Exception e) { throw new ClassNotFoundException(name); } } } Audit2ClassLoader requires a list of URLs to be passed to its constructor. This is really a requirement of URLClassLoader , thus limiting the scope of where Audit2ClassLoader will look for user class files. Classes in the boot, extension, and application class paths will be found and loaded through delegation by the appropriate class loader, as follows .
|
< Day Day Up > |