epubjs
Version:
Render ePub documents in the browser, across many devices
160 lines (157 loc) • 15.3 kB
HTML
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Safety of Implementation</title><link rel="stylesheet" href="core.css" type="text/css"/><meta name="generator" content="DocBook XSL Stylesheets V1.74.0"/></head><body><div class="sect1" title="Safety of Implementation"><div class="titlepage"><div><div><h1 class="title"><a id="learnjava3-CHP-1-SECT-5"/>Safety of Implementation</h1></div></div></div><p><a id="idx10035" class="indexterm"/>It’s one thing to create a language that prevents you from
shooting yourself in the foot; it’s quite another to create one that
prevents others from shooting you in the foot.</p><p><a id="I_indexterm1_id634538" class="indexterm"/> <span class="emphasis"><em>Encapsulation</em></span> is the concept of hiding
data and behavior within a class; it’s an important part of
object-oriented design. It helps you write clean, modular software. In
most languages, however, the visibility of data items is simply part of
the relationship between the programmer and the compiler. It’s a matter of
semantics, not an assertion about the actual security of the data in the
context of the running program’s environment.</p><p><a id="I_indexterm1_id634555" class="indexterm"/>When Bjarne Stroustrup chose the keyword <a id="I_indexterm1_id634562" class="indexterm"/><code class="literal">private</code> to designate
hidden members of classes in C++, he was probably thinking about shielding
a developer from the messy details of another developer’s code, not the
issues of shielding that developer’s classes and objects from attack by
someone else’s viruses and Trojan horses. Arbitrary casting and pointer
arithmetic in C or C++ make it trivial to violate access permissions on
classes without breaking the rules of the language. Consider the following
code:</p><a id="I_1_tt6"/><pre class="programlisting"> <code class="c1">// C++ code</code>
<code class="kd">class</code> <code class="nc">Finances</code> <code class="o">{</code>
<code class="kd">private</code><code class="o">:</code>
<code class="kt">char</code> <code class="n">creditCardNumber</code><code class="o">[</code><code class="mi">16</code><code class="o">];</code>
<code class="o">...</code>
<code class="o">};</code>
<code class="n">main</code><code class="o">()</code> <code class="o">{</code>
<code class="n">Finances</code> <code class="n">finances</code><code class="o">;</code>
<code class="c1">// Forge a pointer to peek inside the class</code>
<code class="kt">char</code> <code class="o">*</code><code class="n">cardno</code> <code class="o">=</code> <code class="o">(</code><code class="kt">char</code> <code class="o">*)&</code><code class="n">finances</code><code class="o">;</code>
<code class="n">printf</code><code class="o">(</code><code class="s">"Card Number = %.16s\n"</code><code class="o">,</code> <code class="n">cardno</code><code class="o">);</code>
<code class="o">}</code></pre><p>In this little C++ drama, we have written some code that violates
the encapsulation of the <code class="literal">Finances</code> class
and pulls out some secret information. This sort of shenanigan—abusing an
untyped pointer—is not possible in Java. If this example seems
unrealistic, consider how important it is to protect the foundation
(system) classes of the runtime environment from similar kinds of attacks.
If untrusted code can corrupt the components that provide access to real
resources such as the filesystem, network, or windowing system, it
certainly has a chance at stealing your credit card numbers.</p><p>If a Java application is to be able to dynamically download code
from an untrusted source on the Internet and run it alongside applications
that might contain confidential information, protection has to extend very
deep. The Java security model wraps three layers of protection around
imported classes, as shown in <a class="xref" href="ch01s05.html#learnjava3-CHP-1-FIG-3" title="Figure 1-3. The Java security model">Figure 1-3</a>.</p><div class="figure"><a id="learnjava3-CHP-1-FIG-3"/><div class="figure-contents"><div class="mediaobject"><a id="I_1_tt7"/><img src="httpatomoreillycomsourceoreillyimages1707598.png" alt="The Java security model"/></div></div><p class="title">Figure 1-3. The Java security model</p></div><p>At the outside, application-level security decisions are made by a
security manager in conjunction with a flexible security policy. A
security manager controls access to system resources such as the
filesystem, network ports, and windowing environment. A security manager
relies on the ability of a class loader to protect basic system classes. A
class loader handles loading classes from local storage or the network. At
the innermost level, all system security ultimately rests on the Java
verifier, which guarantees the integrity of incoming classes.</p><p><a id="I_indexterm1_id634644" class="indexterm"/>The Java bytecode verifier is a fixed part of the Java
runtime system. Class loaders and security managers (or <span class="emphasis"><em>security
policies</em></span> to be more precise), however, are components that may
be implemented differently by different applications, such as servers or
web browsers. All three of these pieces need to be functioning properly to
ensure security in the Java environment.</p><div class="sect2" title="The Verifier"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-1-SECT-5.1"/>The Verifier</h2></div></div></div><p><a id="idx10005" class="indexterm"/> <a id="idx10036" class="indexterm"/> <a id="idx10043" class="indexterm"/>Java’s first line of defense is the <span class="emphasis"><em>bytecode
verifier</em></span>. The verifier reads bytecode before it is run and
makes sure it is well behaved and obeys the basic rules of the Java
language. A trusted Java compiler won’t produce code that does
otherwise. However, it’s possible for a mischievous person to
deliberately assemble bad Java bytecode. It’s the verifier’s job to
detect this.</p><p>Once code has been verified, it’s considered safe from certain
inadvertent or malicious errors. For example, verified code can’t forge
references or violate access permissions on objects (as in our credit
card example). It can’t perform illegal casts or use objects in
unintended ways. It can’t even cause certain types of internal errors,
such as overflowing or underflowing the internal stack. These
fundamental guarantees underlie all of Java’s security.</p><p>You might be wondering, isn’t this kind of safety implicit in lots
of interpreted languages? Well, while it’s true that you shouldn’t be
able to corrupt a BASIC interpreter with a bogus line of BASIC code,
remember that the protection in most interpreted languages happens at a
higher level. Those languages are likely to have heavyweight
interpreters that do a great deal of runtime work, so they are
necessarily slower and more cumbersome.</p><p>By comparison, Java bytecode is a relatively light, low-level
instruction set. The ability to statically verify the Java bytecode
before execution lets the Java interpreter run at full speed later with
full safety, without expensive runtime checks. This was one of the
fundamental innovations in Java.</p><p>The verifier is a type of mathematical “theorem prover.” It steps
through the Java bytecode and applies simple, inductive rules to
determine certain aspects of how the bytecode will behave. This kind of
analysis is possible because compiled Java bytecode contains a lot more type information than
the object code of other languages of this kind. The bytecode also has
to obey a few extra rules that simplify its behavior. First, most
bytecode instructions operate only on individual data types. For
example, with stack operations, there are separate instructions for
object references and for each of the numeric types in Java. Similarly,
there is a different instruction for moving each type of value into and
out of a local variable.</p><p>Second, the type of object resulting from any operation is always
known in advance. No bytecode operations consume values and produce more
than one possible type of value as output. As a result, it’s always
possible to look at the next instruction and its operands and know the
type of value that will result.</p><p>Because an operation always produces a known type, it’s possible
to determine the types of all items on the stack and in local variables
at any point in the future by looking at the starting state. The
collection of all this type information at any given time is called the
<span class="emphasis"><em>type state</em></span> of the stack; this is what Java tries to
analyze before it runs an application. Java doesn’t know anything about
the actual values of stack and variable items at this time; it only
knows what kind of items they are. However, this is enough information
to enforce the security rules and to ensure that objects are not
manipulated illegally.</p><p>To make it feasible to analyze the type state of the stack, Java
places an additional restriction on how Java bytecode instructions are
executed: all paths to the same point in the code must arrive with
exactly the same type state.<a id="I_indexterm1_id634787" class="indexterm"/><a id="I_indexterm1_id634794" class="indexterm"/><a id="I_indexterm1_id634801" class="indexterm"/></p></div><div class="sect2" title="Class Loaders"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-1-SECT-5.2"/>Class Loaders</h2></div></div></div><p><a id="I_indexterm1_id634815" class="indexterm"/> <a id="I_indexterm1_id634824" class="indexterm"/>Java adds a second layer of security with a
<span class="emphasis"><em>class loader</em></span>. A class loader is responsible for
bringing the bytecode for Java classes into the interpreter. Every
application that loads classes from the network must use a class loader
to handle this task.</p><p>After a class has been loaded and passed through the verifier, it
remains associated with its class loader. As a result, classes are
effectively partitioned into separate namespaces based on their origin.
When a loaded class references another class name, the location of the
new class is provided by the original class loader. This means that
classes retrieved from a specific source can be restricted to interact
only with other classes retrieved from that same location. For example,
a Java-enabled web browser can use a class loader to build a separate
space for all the classes loaded from a given URL. Sophisticated
security based on cryptographically signed classes can also be
implemented using class loaders.</p><p>The search for classes always begins with the built-in Java system
classes. These classes are loaded from the locations specified by the
Java interpreter’s <a id="I_indexterm1_id634854" class="indexterm"/><span class="emphasis"><em>classpath</em></span> (see <a class="xref" href="ch03.html" title="Chapter 3. Tools of the Trade">Chapter 3</a>). Classes in the classpath are loaded by
the system only once and can’t be replaced. This means that it’s
impossible for an application to replace fundamental system classes with
its own versions that change their functionality.</p></div><div class="sect2" title="Security Managers"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-1-SECT-5.3"/>Security Managers</h2></div></div></div><p><a id="I_indexterm1_id634878" class="indexterm"/> <a id="I_indexterm1_id634887" class="indexterm"/>A <span class="emphasis"><em>security manager</em></span> is responsible for
making application-level security decisions. A security manager is an
object that can be installed by an application to restrict access to
system resources. The security manager is consulted every time the
application tries to access items such as the filesystem, network ports,
external processes, and the windowing environment; the security manager
can allow or deny the request.</p><p>Security managers are primarily of interest to applications that
run untrusted code as part of their normal operation. For example, a
Java-enabled web browser can run applets that may be retrieved from
untrusted sources on the Net. Such a browser needs to install a security
manager as one of its first actions. This security manager then
restricts the kinds of access allowed after that point. This lets the
application impose an effective level of trust before running an
arbitrary piece of code. And once a security manager is installed, it
can’t be replaced.</p><p>The security manager works in conjunction with an access
controller that lets you implement security policies at a high level by
editing a declarative security policy file. Access policies can be as
simple or complex as a particular application warrants. Sometimes it’s
sufficient simply to deny access to all resources or to general
categories of services, such as the filesystem or network. But it’s also
possible to make sophisticated decisions based on high-level
information. For example, a Java-enabled web browser could use an access
policy that lets users specify how much an applet is to be trusted or
that allows or denies access to specific resources on a case-by-case
basis. Of course, this assumes that the browser can determine which
applets it ought to trust. We’ll discuss how this problem is addressed
through code-signing shortly.</p><p>The integrity of a security manager is based on the protection
afforded by the lower levels of the Java security model. Without the
guarantees provided by the verifier and the class loader, high-level
assertions about the safety of system resources are meaningless. The
safety provided by the Java bytecode verifier means that the interpreter
can’t be corrupted or subverted and that Java code has to use components
as they are intended. This, in turn, means that a class loader can
guarantee that an application is using the core Java system classes and
that these classes are the only way to access basic system resources.
With these restrictions in place, it’s possible to centralize control
over those resources at a high level with a security manager and
user-defined policy.<a id="I_indexterm1_id634944" class="indexterm"/></p></div></div></body></html>