Sponsored Link •
|
Advertisement
|
In Java's sandbox, the class loader architecture is the first line of defense. It is the class loader, after all, that brings code into the Java virtual machine--code that could be hostile or buggy. The class loader architecture contributes to Java's sandbox in three ways:
The class loader architecture prevents malicious code from interfering with benevolent code by
providing separate name-spaces for classes loaded by different class loaders. A name-space is
a set of unique names -- one name for each loaded class -- that the Java virtual machine maintains for each
class loader. Once a Java virtual machine has loaded a class named Volcano
into a
particular name-space, for example, it is impossible to load a different class named
Volcano
into that same name-space. You can load multiple
Volcano
classes into a Java virtual machine, however, because you can create
multiple name-spaces inside a Java application by creating multiple class loaders. If you create three
separate name-spaces (one for each of three class loaders) in a running Java application, then, by loading
one Volcano
class into each name-space, your program could load three different
Volcano
classes into your application.
Name-spaces contribute to security because you can in effect place a shield between classes loaded into different name-spaces. Inside the Java virtual machine, classes in the same name-space can interact with one another directly. Classes in different name-spaces, however, can't even detect each other's presence unless you explicitly provide a mechanism that allows them to interact. If a malicious class, once loaded, had guaranteed access to every other class currently loaded by the virtual machine, that class could potentially learn things it shouldn't know or interfere with the proper execution of your program.
Figure 3-1 shows the name-spaces associated with two class loaders, both of which have loaded a type
named Volcano
. Each name in a name space is associated with the type data in the
method area that defines the type with that name. Figure 3-1 shows arrows from the names in the name-
spaces to the types in the method area that define the type. The class loader on the left, which is shown dark
gray, has loaded the two dark gray types named Climber
and
Volcano
. They class loader on the right, which is shown light gray, has loaded the two
light gray types named BakingSoda
and Volcano
. Because of
the nature of name-spaces, when the Climber
class mentions the
Volcano
class, it refers to the dark gray Volcano
, the
Volcano
loaded in the same name space. It has no way to know that the other
Volcano
, which is sitting in the same virtual machine, even exists. For the details on
how the class loader architecture achieves its separation of namespaces, see Chapter 8, "The Linking
Model."
The class loader architecture guards the borders of the trusted class libraries by making it possible for trusted packages to be loaded with different class loaders than untrusted packages. Although you can grant special access privileges between types belonging to the same package by giving members protected or package access, this special access is granted to members of the same package at runtime only if they were loaded by the same class loader.
Often, a user-defined class loader relies on other class loaders--at the very least, upon the class loaders
created at virtual machine startup--to help it fulfill some of the class load requests that come its way. Prior
to 1.2, class loaders had to explicitly ask for the help of other class loaders. A class loader could ask another
user-defined class loader to load a class by invoking loadClass()
on a reference to
that user-defined class loader. Or, a class loader could ask the bootstrap class loader to attempt to load a
class by invoking findSystemClass()
, a static method defined in class
ClassLoader
. In version 1.2, the process by which one class loader asks another
class loader to try and load a type was formalized into a parent-delegation model. Starting with 1.2, each
class loader except the bootstrap class loader has a "parent" class loader. Before a particular class loader
attempts to load a type in its custom way, it by default "delegates" the job to its parent -- it asks its parent to
try and load the type. The parent, in turn, asks its parent to try and load the type. The delegation process
continues all the way up to the bootstrap class loader, which is in general the last class loader in the
delegation chain. If a class loader's parent class loader is able to load a type, the class loader returns that
type. Else, the class loader attempts to load the type itself.
In most Java virtual machine implementations prior to 1.2, the built-in class loader (which was then called the primordial class loader) was responsible for loading locally available class files. Such class files usually included the class files that made up the Java application being executed plus any libraries needed by the application, including the class files of the Java API. Although the manner in which the class files for requested types were located was implementation specific, many implementations searched directories and JAR files in an order specified by a class path.
In 1.2, the job of loading locally available class files was parceled out to multiple class loaders. The built-in class loader, previously called the primordial class loader, was renamed the "bootstrap" class loader to indicate that it was now responsible for loading only the class files of the core Java API. The name bootstrap class loader comes from the idea that the class files of the core Java API are the class files required to "bootstrap" the Java virtual machine.
Responsibility for loading other class files, such as the class files for the application being executed, class files for installed or downloaded standard extensions, class files for libraries discovered in the class path, and so on, was given in 1.2 to user-defined class loaders. When a 1.2 Java virtual machine starts execution, therefore, it creates at least one and probably more user-defined class loaders before the application even starts. All of these class loaders are connected in one chain of parent-child relationships. At the top of the chain is the bootstrap class loader. At the bottom of the chain is what came in 1.2 to be called the "system class loader." Prior to 1.2, the name "system class loader" was sometimes used to refer to the built-in class loader, which was also called the primordial class loader. In 1.2, the name system class loader was more formally defined to mean the default delegation parent for new user-defined class loaders created by a Java application. This default delegation parent is usually going to be the user-defined class loader that loaded the initial class of the application, but may be any user-defined class loader decided upon by the designers of the Java Platform implementation.
For example, imagine you write a Java application that installs a class loader whose particular manner of loading class files is by downloading them across a network. Imagine you run this application on a virtual machine that instantiates two user-defined class loaders on startup: an "installed extensions" class loader and a "class path" class loader. These class loaders are connected in a parent-child relationship chain along with the bootstrap class loader as shown in Figure 3-2. The class path's class loader's parent is the installed extensions class loader, whose parent is the bootstrap class loader. As shown in Figure 3-2, the class path class loader is designated as the system class loader, the default delegation parent for new user-defined class loaders instantiated by the application. Assume that when you application instantiates its network class loader, it specifies the system class loader as its parent.
Imagine that during the course of running the Java application, a request is made of your class loader to
load a class named Volcano
. Your class loader would first ask its parent, the class
path class loader, to find and load the class. The class path class loader, in turn, would make the same
request of its parent, the installed extensions class loader. This class loader, would also first delegate the
request to its parent, the bootstrap class loader. Assuming that class Volcano
is not a
part of the Java API, an installed extension, or on the class path, all of these class loaders would return
without supplying a loaded class named Volcano
. When the class path class loader
responds that neither it nor any of its parents can load the class, your class loader could then attempt to load
the Volcano
class in its custom manner, by downloading it across the network.
Assuming your class loader was able to download class Volcano
, that
Volcano
class could then play a role in the application's future course of execution.
To continue with the same example, assume that at some time later a method of class
Volcano
is invoked for the first time, and that method references class java.util.HashMap
from the Java API. Because it is the first time the reference
was used by the running program, the virtual machine asks your class loader (the one that loaded
Volcano
) to load java.util.HashMap
. As before, your class
loader first passes the request to its parent class loader, and the request gets delegated all the way up to the
bootstrap class loader. But in this case, the bootstrap class loader is able to return a
java.util.Hashmap
class back to your class loader. Since the bootstrap class
loader was able to find the class, the installed extensions class loader doesn't attempt to look for the type in
the installed extensions. The class path class loader doesn't attempt to look for the type on the class path.
And your class loader doesn't attempt to download it across the network. All of these class loaders merely
return the java.util.HashMap
class returned by the bootstrap class loader. From
that point forward, the virtual machine uses that java.util.HashMap
class
whenever class Volcano
references a class named
java.util.HashMap
.
Given this background into how class loaders work, you are ready to look at how class loaders can be used to protect trusted libraries. The class loader architecture guards the borders of the trusted class libraries by preventing untrusted classes from pretending to be trusted. If a malicious class could successfully trick the Java virtual machine into believing it was a trusted class from the Java API, that malicious class could potentially break through the sandbox barrier. By preventing untrusted classes from impersonating trusted classes, the class loader architecture blocks one potential approach to compromising the security of the Java runtime.
Given the parent-delegation model, the bootstrap class loader is able to attempt to load types before the
standard extensions class loader, which is able to attempt to load types before the class path class loader,
which is able to attempt to load types before your network class loader. Thus, given the manner in which the
parent-child delegation chain is built, the most trusted library, the core Java API, is checked first for each
type. After that, the standard extensions are checked. After that, local class files that are sitting on the class
path are checked. So if some mobile code loaded by your network class loader wants to download a type
across the network with the same name as something in the Java API, such as
java.lang.Integer
, it won't be able to do it. If a class file for
java.lang.Integer
exists in the Java API, it will be loaded by the bootstrap class
loader. The network class loader will not attempt to download and define a class named
java.lang.Integer
. It will just use the type returned by its parent, the one loaded
by the bootstrap class loader. In this way, the class loader architecture prevents untrusted code from
replacing trusted classes with their own versions.
But what if the mobile code, rather than trying to replace a trusted type, wants to insert a brand new
type into a trusted package? Imagine what would happen if your network class loader from the previous
example was requested to load a class named java.lang.Virus
. As before, this
request would first be delegated all the way up the parent-child chain to the bootstrap class loader. Although
the bootstrap class loader is responsible for loading the class files of the core Java API, which includes a
package named, java.lang
, it is unable to find a member of the
java.lang
package with the name, Virus
. Assuming this class
was also not found among the installed extensions or on the local class path, your class loader would
proceed to attempt to download the type across the network.
Assume your class loader is successful in the download attempt and defines the type named
java.lang.Virus
. Java allows classes in the same package to grant each other
special access privileges that aren't granted to classes outside the package. So, since your class loader has
loaded a class that by its name (java.lang.Virus
) brazenly declares itself to be
part of the Java API, you might expect it could gain special access to the trusted classes of
java.lang
and could possibly use that special access for devious purposes. The class
loader mechanism thwarts this code from gaining special access to the trusted types in the
java.lang
package, because the Java virtual machine only grants that special
package access between types loaded into the same package by the same class loader. Since the trusted class
files of the Java API's java.lang
package were loaded by the bootstrap class loader,
and the malicious java.lang.Virus
class was loaded by your network class loader,
these types do not belong to the same runtime package. The term runtime package, which first
appeared in the second edition of the Java Virtual Machine Specification, refers to a set of types that belong
to the same package and that were all loaded by the same class loader. Before allowing access to package-
visible members (members declared with protected or package access) between two types, the virtual
machine makes sure not only that the two types belong to the same package, but that they belong to the
same runtime package -- that they were loaded by the same class loader. Thus, because
java.lang.Virus
and the members of java.lang
from the
core Java API don't belong to the same runtime package, java.lang.Virus
can't
access the package-visible members and types of the Java API's java.lang
package.
This concept of a runtime package is one motivation for using different class loaders to load different kinds of classes. The bootstrap class loader loads the class files of the core Java API. These class files are the most trusted. An installed extensions class loader loads class files from any installed extensions. Installed extensions are quite trusted, but need not be trusted to the extent that they are allowed to gain access to package-visible members of the Java API by simply inserting new types into those packages. Because installed extensions are loaded with a different class loader than the core API, they can't. Likewise, code found on the class path by the class path class loader can't gain access to package-visible members of the installed extensions or the Java API.
Another way class loaders can be used to protect the borders of trusted class libraries is by simply
disallowing the loading of certain forbidden types. For example, you may have installed some packages that
contain classes you want your application to be able to load through your network class loader's parent, the
class path class loader, but not through your own network class loader. Assume you have created a package
named absolutepower
and installed it somewhere on the local class path, where it
is accessible by the class path class loader. Assume also that you don't want classes loaded by your class
loader to be able to load any class from the absolutepower
package. In this case,
you would write your class loader such that the very first thing it does is make sure the requested class
doesn't declare itself as a member of the absolutepower
package. If such a class is
requested, your class loader, rather than passing the class name to its parent class loader, would throw a
security exception.
The only way a class loader can know whether or not a class is from a forbidden package, such as
absolutepower
, is by the class's name. Thus a class loader must have a list of the
names of forbidden packages. Because the name of class
absolutepower.FancyClassLoader
indicates it is part of the
absolutepower
package, and the absolutepower
package is
on the list of forbidden packages, your class loader should throw a security exception absolutely.
Besides shielding classes in different namespaces and protecting the borders of trusted class libraries, class loaders play one other security role. Class loaders must place each loaded class into a protection domain, which defines what permissions the code is going to be given as it runs. More information about this vitally important security job of class loaders will be given later in this chapter.
Sponsored Links
|