📄 chap03.html
字号:
<H3><EM><P>The Sandbox</P>
</EM></H3><P>Traditionally, you had to trust software before you ran it. You achieved security by being careful only to use software from trusted sources, and by regularly scanning for viruses just to make sure. Once some software got access to your system, it had full reign. If it was malicious, it could do a great deal of damage because there were no restrictions placed on it by the runtime environment of your computer. So in the traditional security scheme, you tried to prevent malicious code from ever gaining access to your computer in the first place.</P>
<P>The sandbox security model makes it easier to work with software that comes from sources you don韙 fully trust. Instead of approaching security by requiring you to prevent any code you don韙 trust from ever making its way onto your computer, the sandbox model allows you to welcome code from any source. But as code from an untrusted source runs, the sandbox restricts code from untrusted sources from taking any actions that could possibly harm your system. You don韙 need to figure out what code you can and can韙 trust. You don韙 need to scan for viruses. The sandbox itself prevents any viruses or other malicious code you may invite into your computer from doing any damage.</P>
<P>If you have a properly skeptical mind, you韑l need to be convinced a sandbox has no leaks before you trust it to protect you. To make sure the sandbox must has no leaks, Java韘 security model involves every aspect of its architecture. If there were areas in Java韘 architecture where security was weak, a malicious programmer (a "cracker") could potentially exploit those areas to "go around" the sandbox. To understand the sandbox, therefore, you must look at several different parts of Java韘 architecture, and understand how they work together.</P>
<P>The fundamental components responsible for Java韘 sandbox are:</P>
<UL><LI> the class loader architecture
<LI> the class file verifier
<LI> safety features built into the Java Virtual Machine (and the language)
<LI> the security manager and the Java API</UL>
<P>One of the greatest strengths of Java韘 security model is that two of these components, the class loader and the security manager, are customizable. By customizing these components, you can create a customized security policy for a Java application. As a developer, you may never need to create your own customized sandbox. You can often make use of sandboxes created by others. When you write and run a Java applet, for instance, you make use of a sandbox created by the developers of the web browser that hosts your applet.</P>
<H3><EM><P>The Class Loader Architecture</P>
</EM></H3><P>In Java韘 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. The class loader architecture contributes to Java韘 sandbox in two ways:</P>
<OL><LI>it guards the borders of the trusted class libraries, and</P>
<LI>it prevents malicious code from interfering with benevolent code.</OL>
<P>The class loader architecture guards the borders of the trusted class libraries by preventing an 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.</P>
<P>The class loader architecture prevents malicious code from interfering with benevolent code by providing protected name-spaces for classes loaded by different class loaders. A <I>name-space</I> is a set of unique names for loaded classes that is maintained by the Java Virtual Machine. Once a Java Virtual Machine has loaded a class named <FONT FACE="Courier New">Volcano</FONT> into a particular name-space, for example, it is impossible to load a different class named <FONT FACE="Courier New">Volcano</FONT> into that same name-space. You can load multiple <FONT FACE="Courier New">Volcano</FONT> 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 <FONT FACE="Courier New">Volcano</FONT> class into each name-space, your program could load three different <FONT FACE="Courier New">Volcano</FONT> classes into your application.</P>
<P>Name-spaces contribute to security because you can 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韙 even detect each other韘 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韙 know or interfere with the proper execution of your program.</P>
<P>Often, a class loader object relies on other class loaders--at the very least, upon the primordial class loader--to help it fulfill some of the class load requests that come its way. 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. Assume that during the course of running the Java application, a request is made of your class loader to load a class named <FONT FACE="Courier New">Volcano</FONT>. One way you could write the class loader is to have it first asks the primordial class loader to find and load the class from its trusted repository. In this case, since <FONT FACE="Courier New">Volcano</FONT> is not a part of the Java API, assume the primordial class loader can韙 find a class named <FONT FACE="Courier New">Volcano</FONT>. When the primordial class loader responds that it can韙 load the class, your class loader could then attempt to load the <FONT FACE="Courier New">Volcano</FONT> class in its custom manner, by downloading it across the network. Assuming your class loader was able to download class <FONT FACE="Courier New">Volcano</FONT>, that <FONT FACE="Courier New">Volcano</FONT> class could then play a role in the application韘 future course of execution. </P>
<P>To continue with the same example, assume that at some time later a method of class <FONT FACE="Courier New">Volcano</FONT> is invoked for the first time, and that method references class <FONT FACE="Courier New">String</FONT> 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 <FONT FACE="Courier New">Volcano</FONT>) to load <FONT FACE="Courier New">String</FONT>. As before, your class loader first passes the request to the primordial class loader, but in this case, the primordial class loader is able to return a <FONT FACE="Courier New">String</FONT> class back to your class loader. (The primordial class loader most likely didn韙 have to actually load <FONT FACE="Courier New">String</FONT> at this point because, given that <FONT FACE="Courier New">String</FONT> is such a fundamental class in Java programs, it was almost certainly used before and therefore already loaded. Most likely, the primordial class loader just returned the <FONT FACE="Courier New">String</FONT> class that it had previously loaded from the trusted repository.) Since the primordial class loader was able to find the class, your class loader doesn韙 attempt to download it across the network; it merely passes to the virtual machine the <FONT FACE="Courier New">String</FONT> class returned by the primordial class loader. From that point forward, the virtual machine uses that <FONT FACE="Courier New">String</FONT> class whenever class <FONT FACE="Courier New">Volcano</FONT> references a class named <FONT FACE="Courier New">String</FONT>.</P>
<P>When you write a class loader, you create a new environment in which the loaded code runs. If you want the environment to be free of security holes, you must follow certain rules when you write your class loader. In general, you will want to write class loaders such that they protect the borders of trusted class libraries, such as those of the Java API.</P>
<P>Java allows classes in the same package to grant each other special access privileges that aren韙 granted to classes outside the package. So, if your class loader receives a request to load a class that by its name brazenly declares itself to be part of the Java API (for example, a class named <FONT FACE="Courier New">java.lang.Virus</FONT>), it could gain special access to the trusted classes of <FONT FACE="Courier New">java.lang</FONT> and could possibly use that special access for devious purposes. Consequently, you would normally write a class loader so that it simply refuses to load any class that claims to be part of the Java API (or any other trusted runtime library), but that doesn韙 exist in the local trusted repository. In other words, after your class loader passes a request to the primordial class loader, and the primordial class loader indicates it can韙 load the class, your class loader should check to make sure the class doesn韙 declare itself to be a member of a trusted package. If it does, your class loader, instead of trying to download the class across the network, should throw a security exception.</P>
<P>In addition, you may have installed some packages in the trusted repository that contain classes you want your application to be able to load through the primordial class loader, but that you don韙 want to be accessible to classes loaded through your class loader. For example, assume you have created a package named <FONT FACE="Courier New">absolutepower</FONT> and installed it on the local repository accessible by the primordial class loader. Assume also that you don韙 want classes loaded by your class loader to be able to load any class from the <FONT FACE="Courier New">absolutepower</FONT> 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韙 declare itself as a member of the <FONT FACE="Courier New">absolutepower</FONT> package. If such a class is requested, your class loader, rather than passing the class name to the primordial class loader, should throw a security exception.</P>
<P>The only way a class loader can know whether or not a class is from a restricted package, such as <FONT FACE="Courier New">java.lang</FONT>, or a forbidden package, such as <FONT FACE="Courier New">absolutepower</FONT>, is by the class韘 name. Thus a class loader must be given a list of the names of restricted and forbidden packages. Because the name of class <FONT FACE="Courier New">java.lang.Virus</FONT> indicates it is from the <FONT FACE="Courier New">java.lang</FONT> package, and <FONT FACE="Courier New">java.lang</FONT> is on the list of restricted packages, your class loader should throw a security exception if the primordial class loader can韙 load it. Likewise, because the name of class <FONT FACE="Courier New">absolutepower.FancyClassLoader</FONT> indicates it is part of the <FONT FACE="Courier New">absolutepower</FONT> package, and the <FONT FACE="Courier New">absolutepower</FONT> package is on the list of forbidden packages, your class loader should throw a security exception absolutely.</P>
<P>A common way, therefore, to write a security-minded class loader is using the following four steps:</P>
<OL><LI>If packages exist that this class loader is not allowed to load from, the class loader checks whether the requested class is in one of those forbidden packages. If so, it throws a security exception. Else, it continues on to step two.</P>
<LI>The class loader passes the request to the primordial class loader. If the primordial class loader successfully returns the class, the class loader returns that same class. Else, it continues on to step three.</P>
<LI>If trusted packages exist that this class loader is not allowed to add classes to, the class loader checks whether the requested class is in one of those restricted packages. If so, it throws a security exception. Else, it continues on to step four.</P>
<LI>Finally, the class loader attempts to load the class in the custom way, such as by downloading it across a network. If successful, it returns the class. Else, it throws a "no class definition found" error.</OL>
<P>By performing steps one and three as outlined above, the class loader guards the borders of the trusted packages. With step one, it prevents a class from a forbidden package to be loaded at all. With step three, it doesn韙 allow an untrusted class to insert itself into a trusted package.</P>
<H3><EM><P>The Class File Verifier</P>
</EM></H3><P>Working in conjunction with the class loader, the class file verifier ensures that loaded class files have a proper internal structure. If the class file verifier discovers a problem with a class file, it throws an exception. Although compliant Java compilers should not generate malformed class files, a Java Virtual Machine can韙 tell how a particular class file was created. Because a class file is just a sequence of binary data, a virtual machine can韙 know whether a particular class file was generated by a well-meaning Java compiler or by shady crackers bent on compromising the integrity of the virtual machine. As a consequence, all Java Virtual Machine implementations have a class file verifier that can be invoked on untrusted classes, to make sure the classes are safe to use.</P>
<P>One of the security goals that the class file verifier helps achieve is program robustness. If a buggy compiler or savvy cracker generated a class file that contained a method whose bytecodes included an instruction to jump beyond the end of the method, that method could, if it were invoked, cause the virtual machine to crash. Thus, for the sake of robustness, it is important that the virtual machine verify the integrity of the bytecodes it imports. Although Java Virtual Machine designers are allowed to decide when their virtual machines will perform these checks, many implementations will do most checking just after a class is loaded. Such a virtual machine, rather than checking every time it encounters a jump instruction as it executes bytecodes, analyzes bytecodes (and verifies their integrity) once, before they are ever executed. As part of its verification of bytecodes, the Java Virtual Machine makes sure all jump instructions cause a jump to another valid instruction in the bytecode stream of the method. In most cases, checking all bytecodes once, before they are executed, is a more efficient way to guarantee robustness than checking every bytecode instruction every time it is executed.</P>
<P>A class file verifier that performs its checking as early as possible most likely operates in two distinct phases. During phase one, which takes place just after a class is loaded, the class file verifier checks the internal structure of the class file, including verifying the integrity of the bytecodes it contains. During phase two, which takes place as bytecodes are executed, the class file verifier confirms the existence of symbolically referenced classes, fields, and methods. </P>
<H3><P>Phase One: Internal Checks</P>
</H3><P>During phase one, the class file verifier checks everything that韘 possible to check in a class file by looking at only the class file itself. In addition to verifying the integrity of the bytecodes during phase one, the verifier performs many checks for proper class file format and internal consistency. For example, every class file must start with the same four bytes, the magic number: <FONT FACE="Courier New">0xCAFEBABE</FONT>. The purpose of magic numbers is to make it easy for file parsers to recognize a certain type of file. Thus, the first thing a class file verifier likely checks is that the imported file does indeed begin with <FONT FACE="Courier New">0xCAFEBABE</FONT>.</P>
<P>The class file verifier also checks to make sure the class file is neither truncated nor enhanced with extra trailing bytes. Although different class files can be different lengths, each individual component contained inside a class file indicates its length as well as its type. The verifier can use the component types and lengths to determine the correct total length for each individual class file. In this way, it can verify that the imported file has a length consistent with its internal contents.</P>
<P>The verifier also looks at individual components, to make sure they are well-formed instances of their type of component. For example, a method descriptor (its return type and the number and types of its parameters) is stored in the class file as a string that must adhere to a certain context-free grammar. One check the verifier performs on individual components is to make sure each method descriptor is a well-formed string of the appropriate grammar.</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -