Policy-Directed Code Safety
                             by
                       David E. Evans
                              
                              
      S.B. Massachusetts Institute of Technology (1994)
      S.M. Massachusetts Institute of Technology (1994)
                              
                              
  Submitted to the Department of Electrical Engineering and
                      Computer Science
in partial fulfillment of the requirements for the degree of
                              
                    Doctor of Philosophy
                           at the
            Massachusetts Institute of Technology
                              
                        February 2000
                              
                              
  (C) Massachusetts Institute of Technology 1999.  All rights
                          reserved.
                              
                              
                              
                              
                              
                              
                              
                              
Author......................................................
                                                 David Evans
   Department of Electrical Engineering and Computer Science
                                            October 19, 1999
                                                            
                                                            
Certified by................................................
                                              John V. Guttag
                                 Professor, Computer Science
                                           Thesis Supervisor
                                                            
                                                            
Accepted by.................................................
                                             Arthur C. Smith
       Chairman, Departmental Committee on Graduate Students
                                                            
                              
                 Policy-Directed Code Safety
                             by
                       David E. Evans
                              
  Submitted to the Department of Electrical Engineering and
 Computer Science in partial fulfillment of the requirements
           for the degree of Doctor of Philosophy
                              

Abstract

Executing code can be dangerous.  This thesis describes a
scheme for protecting the user by constraining the behavior
of an executing program.  We introduce Naccio, a general
architecture for constraining the behavior of program
executions.  Naccio consists of languages for defining
safety policies in a platform-independent way and a system
architecture for enforcing those policies on executions by
transforming programs.  Prototype implementations of Naccio
have been built that enforce policies on JavaVM classes and
Win32 executables.

Naccio addresses two weaknesses of current code safety
systems.  One problem is that current systems cannot enforce
policies with sufficient precision.  For example, a system
such as the Java sandbox cannot enforce a policy that limits
the rate at which data is sent over the network without
denying network use altogether since there are no safety
checks associated with sending data.  The problem is more
fundamental than simply the choices about which safety
checks to provide.  The system designers were hamstrung into
providing only a limited number of checks by a design that
incurs the cost of a safety check regardless of whether it
matters to the policy in effect.  Because Naccio statically
analyzes and compiles a policy, it can support safety checks
associated with any resource manipulation, yet the costs of
a safety check are incurred only when the check is relevant.

Another problem with current code safety systems is that
policies are defined in ad hoc and platform-specific ways.
The author of a safety policy needs to know low-level
details about a particular platform and once a safety policy
has been developed and tested it cannot easily be
transferred to a different platform.  Naccio provides a
platform-independent way of defining safety policies in
terms of abstract resources.  Safety policies are described
by writing code fragments that account for and constrain
resource manipulations.  Resources are described using
abstract objects with operations that correspond to
manipulations of the corresponding system resource.  A
platform interface provides an operational specification of
how system calls affect resources.  This enables safety
policies to be described in a platform-independent way and
isolates most of the complexity of the system.

This thesis motivates and describes the design of Naccio,
demonstrates how a large class of safety policies can be
defined, and evaluates results from our experience with the
prototype implementations.






Thesis Supervisor: John V. Guttag
Title: Professor, Computer Science





Acknowledgements



John Guttag is that rare advisor who has the ability to
direct you to see the big picture when you are mired details
and to get you to focus when you are distracted by
irrelevancies.  John has been my mentor throughout my
graduate career, and there is no doubt that I wouldn't be
finishing this thesis this millennium without his guidance.

As my readers, John Chapin and Daniel Jackson were helpful
from the early proposal stages until the final revisions.
Both clarified important technical issues, gave me ideas
about how to improve the presentation, and provided copious
comments on drafts of this thesis.

Andrew Twyman designed and implemented Naccio/Win32.  His
experience building Naccio/Win32 helped clarify and develop
many of the ideas in this thesis, and his insights were a
significant contribution to this thesis.

During my time at MIT, I've at the good fortune to work with
many interesting and creative people.  The MIT Laboratory
for Computer Science and the Software Devices and Systems
group provided a pleasant and dynamic research environment.
Much of what I learned as a grad student was through
spontaneous discussions with William Adjie-Winoto, John
Ankcorn, Anna Chefter, Dorothy Curtis, Stephen Garland,
Angelika Leeb, Ulana Legedza, Li-wei Lehman, Victor
Luchangco, Andrew Myers, Anna Pogosyants, Bodhi Priyantha,
Hariharan Rahul, Michael Saginaw, Raymie Stata, Yang Meng
Tan, Van Van, David Wetherall, and Charles Yang.  This work
has also benefited from discussions with Ulfar Erlingsson
and Fred Schneider from Cornell, Raju Pandey from UC Davis,
Dan Wallach from Rice University, Mike Reiter from Lucent
Bell Laboratories, and David Bantz from IBM Research.

Geoff Cohen wrote the JOIE toolkit used as Naccio/JavaVM's
transformation engine and made its source code available to
the research community.  He provided quick answers to all my
questions about using and modifying JOIE.

Finally, I thank my parents for their constant encouragement
and support.  I couldn't ask for two better role models.



                              
                              
                              
                      Table of Contents
                              
                              

1 Introduction                                            9
 1.1 Threats and Countermeasures                        10
 1.2 Background                                         13
 1.3 Design Goals                                       14
   1.3.1 Security                                       16
   1.3.2 Versatility                                    16
   1.3.3 Ease of Use                                    17
   1.3.4 Ease of Implementation                         17
   1.3.5 Efficiency                                     18
 1.4 Contributions                                      18
 1.5 Overview of Thesis                                 19

2 Naccio Architecture                                    21
 2.1 Overview                                           21
 2.2 Policy Compiler                                    23
 2.3 Program Transformer                                24
 2.4 Walkthrough Example                                26

3 Defining Safety Policies                               29
 3.1 Resource Descriptions                              29
   3.1.1 Resource Operations                            30
   3.1.2 Resource Groups                                32
 3.2 Safety Properties                                  33
   3.2.1 Adding State                                   33
   3.2.2 Use Limits                                     34
   3.2.3 Composing Properties                           35
 3.3 Standard Resource Library                          36
 3.4 Policy Expressiveness                              39

4 Describing Platforms                                   41
 4.1 Platform Interfaces                                41
 4.2 Java API Platform Interface                        43
   4.2.1 Platform Interface Level                       43
   4.2.2 File Classes                                   45
   4.2.3 Network Classes                                48
   4.2.4 Extended Safety Policies                       49
 4.3 Win32 Platform Interface                           52
   4.3.1 Platform Interface Level                       53
   4.3.2 Prototype Platform Interface                   54
 4.4 Expressiveness                                     55

5 Compiling Policies                                     57
 5.1 Processing the Resource Use Policy                 57
 5.2 Processing the Platform Interface                  59
 5.3 Generating Resource Implementations                60
   5.3.1 Naccio/JavaVM                                  61
   5.3.2 Naccio/Win32                                   62
 5.4 Generating Platform Interface Wrappers             65
   5.4.1 Naccio/JavaVM                                  65
   5.4.2 Naccio/Win32                                   71
 5.5 Integrated Optimizations                           71
 5.6 Policy Description File                            73

6 Transforming Programs                                  75
 6.1 Replacing System Calls                             75
   6.1.1 Naccio/JavaVM                                  75
   6.1.2 Naccio/Win32                                   77
   6.1.3 Other Platforms                                78
 6.2 Guaranteeing Integrity                             78
   6.2.1 Naccio/JavaVM                                  79
   6.2.2 Naccio/Win32                                   81

7 Related Work                                           85
 7.1 Low-Level Code Safety                              85
 7.2 Language-Based Code Safety Systems                 86
 7.3 Reference Monitors                                 89
   7.3.1 Java Security Manager                          89
   7.3.2 Interposition Systems                          90
   7.3.3 Transformation-based Systems                   93
 7.4 Code Transformation Engines                        94
   7.4.1 Java Transformation Tools                      94
   7.4.2 Win32 Transformation Tools                     95

8 Evaluation                                             97
 8.1 Security                                           97
 8.2 Versatility                                        99
   8.2.1 Theoretical Limitations                       100
   8.2.2 Policy Expressiveness                         100
 8.3 Ease of Use                                       105
 8.4 Ease of Implementation                            106
 8.5 Efficiency                                        108
   8.5.1 Test Policies                                 108
   8.5.2 Policy Compilation                            109
   8.5.3 Application Transformation                    112
   8.5.4 Execution                                     112

9 Future Work                                           121
 9.1 Improving Implementations                         121
   9.1.1 Assurance                                     121
   9.1.2 Complete Implementations                      122
   9.1.3 Performance Improvements                      123
 9.2 Extensions                                        124
 9.3 Deployment                                        126
 9.4 Other Applications                                128

10 Summary and Conclusion                               131
 10.1 Summary                                          131
 10.2 Conclusion                                       132

References                                              133


                              
                       List of Figures

Figure 1.  Naccio Architecture.                          22
Figure 2.  Wrapped system call sequence.                 25
Figure 3.  Interaction diagram for enforcing LimitWrite. 27
Figure 4.  File System Resources.                        31
Figure 5.  NoBashingFiles property.                      34
Figure 6.  LimitBytesWritten Safety Property.            35
Figure 7.  LimitWrite resource use policy.               36
Figure 8.  Network Resources.                            38
Figure 9.  Platform interface wrapper for java.io.File
   class.                                               46
Figure 10.  RFileMap helper class.                       46
Figure 11.  Platform Interface wrapper for
   java.io.FileOutputStream class.                      47
Figure 12.  Platform interface for java.net.Socket.      48
Figure 13.  NCheckedNetworkOutputStream helper class.    49
Figure 14.  Policy that limits network send rate by delaying
   transmissions.                                       50
Figure 15.  Policy that limits bandwidth by splitting up and
   delaying network sends.                              51
Figure 16.  RegulatedSendSocket wrapper modification code.51
Figure 17.  NRegulatedOutputStream helper class (excerpted).
   52
Figure 18.  Naccio/Win32 platform interface wrapper for
   DeleteFileA.                                         54
Figure 19.  Resource class generated by Naccio/JavaVM.   62
Figure 20.  Resource headers file generated by Naccio/Win32.
   63
Figure 21.  Implementation resource.c generated by
   Naccio/Win32 for LimitWrite.                         64
Figure 22.  Pass-through semantics.                      68
Figure 23.  Generated policy-enforcing library class for
   java.io.FileOutputStream.                            70
Figure 24.  Results for jlex benchmark.                 116
Figure 25.  Results for tar execution benchmark.        117
Figure 26.  Results for ftpmirror execution benchmark.  118

                              
                       List of Tables

Table 1.  Policy compilation costs.                     110
Table 2.  Program transformer results.                  112
Table 3.  Micro-benchmark performance.                  114
Table 4.  Benchmark checking.                           115


                                                            


Chapter 1
Introduction



Traditional computer security has focused on assuring
confidentiality, integrity and availability.
Confidentiality means hiding information from unauthorized
users; integrity means preventing unauthorized modifications
of data; and availability means preventing an attacker from
making a resource unavailable to legitimate users.  Military
and large commercial systems operators are (or at least
should be) willing to spend large amounts of effort and
money as well as to risk inconveniencing their users in
order to provide satisfactory confidentiality, integrity and
availability assurances.

The security concerns for typical home and non-critical
business users are very different.  In the past, these users
had limited security concerns.  Since they were typically
not connected to a network, their primary concern was
viruses on software distributed on floppy disks.  Although
viruses could be a considerable annoyance, users who stuck
to shrink wrapped software were unlikely to encounter
viruses, and the damage was limited to destroying files (or
occasionally hardware) on a single machine.

Today, nearly all computers are connected to the public
Internet much of the time.  Although the benefits of
connectivity are unquestioned, being on a network introduces
significant new security risks.  The damage a program can do
is no longer limited to damaging local data or hardware --- it
can send personal information through the global Internet,
damaging the operator's reputation or finances.
Furthermore, the likelihood of executing an untrustworthy
program is dramatically increased.  The ease of distributing
code on the Internet means users often have little or no
knowledge about the origin of the code they choose to run.
In addition, it is becoming hard to distinguish the
``programs'' from the ``data'' --- Java applets embedded in web
pages can run unbeknownst to the user; documents can contain
macros that access the file system and network; and email
messages can contain attachments that are arbitrary
executables.

The solution in high security environments is to turn off
all mobile code and only run validated programs from trusted
sources.  This can be done by configuring browsers and other
applications to disallow active contents such as Java
applets and macros, or by installing a firewall that
monitors all network traffic and drops packets that may
contain untrustworthy code.  This solution sacrifices the
convenience and utility of the network, and would be
unacceptable in many environments.  Instead, solutions
should allow possibly untrustworthy programs to run, but
allow the user to place precise limits on what they may do.
In such an environment, security mechanisms must be
inexpensive and unobtrusive.  Anecdotal evidence suggests
that any code safety system that places a burden on its
users will be quickly disabled, since its benefits are only
apparent in the extraordinary cases in which a program is
behaving dangerously.

A code safety system provides confidence that a program
execution will not do certain undesirable things.  Although
much progress has been made toward this goal in the last few
years, current systems are still unsatisfactory.  This work
seeks to address two important weaknesses of existing code
safety systems:

1.   They cannot enforce sufficiently precise policies.
     This means either a program is allowed to do harmful things,
     or users are unable to run some useful programs.  For
     example, a system like the Java sandbox cannot enforce a
     policy that limits the number of bytes that may be written
     to the file system without preventing writing completely.
     This is a result of the limited locations where safety
     checking can be done.  The designers were forced to select a
     small number of security-relevant operations that can have
     safety checking since the overhead of a safety check is
     always suffered even if the policy in effect places no
     constraints on the security-relevant operation.

2.   The mechanisms they provide for defining safety
     policies are ad hoc and platform-specific.  Ad hoc policy
     definition mechanisms limit the policies that can be defined
     to the class of policies considered by the system designers.
     It is impossible to anticipate all possible attacks or
     security requirements, so ad hoc policy definition
     mechanisms are inevitably vulnerable to new attacks.  Tying
     policy definition to a particular execution platform means
     that policy authors need to know intimate details about that
     platform, and there is no opportunity to reuse policies on
     different execution platforms.  This is a problem for policy
     authors, but also limits what policies are available to
     users.  Further, it increases the gap between those people
     capable of writing and understanding policies and those who
     must trust a provided definition.

This thesis demonstrates that it is possible to produce a
code safety system that does not suffer from these
weaknesses without sacrificing convenience or efficiency.
We describe Naccio1, an architecture for code safety, and
report on two prototype implementations: Naccio/JavaVM that
enforces policies on JavaVM classes, and Naccio/Win32 that
enforces policies on Win32 executables.  Naccio defines
policies by associating checking code with abstract resource
manipulations.  A Naccio implementation includes an
operational specification of an execution platform in terms
of those abstract resource manipulations.  Naccio enforces
policies by transforming programs to interpose checking code
around security-critical operations.

1.1  Threats and Countermeasures

No security system can prevent all types of threats.  Our
focus is on threats stemming from executing programs.  We
ignore threats that do not result from a legitimate user
running a program including compromised authentications and
physical security breeches.

Different kinds of threats call for different
countermeasures.  Countermeasures for threats related to
program executions come in two basic forms: restrictions on
which programs may run, and constraints on what executions
may do.  Restrictions on which programs may run can be based
on trust and cryptography (only run programs that are
cryptographically signed by someone I trust), or based on
static analysis that proves a program does not have certain
undesired properties (only run programs that a virus
detector checks do not contain instruction sequences
matching known viruses).  Constraints on what executions may
do can be expressed as a policy.2  The policy that should be
enforced on an execution depends on how much trust the user
has in the program and how much knowledge is available about
its expected behavior.  Ideally, all executions would run
with a policy that limits them to exactly the behavior
deemed acceptable for that program.  This is not possible,
however, since users cannot be expected to research and
encode the limits of expected behavior for every program
before running it.  Instead, we should use different
policies as countermeasures to different types of threats.
Threats where code safety is an important countermeasure
include viruses, Trojan horses, faulty programs and user
mistakes.

     Viruses
Viruses are code fragments that propagate themselves
automatically.  The damage they cause ranges from causing a
minor annoyance to destroying hard drives and distributing
confidential information.  Every few weeks a new virus
attack is reported widely in the mainstream media
[NYTimes99a, NYTimes99b, NYTimes99c].

Although early computer viruses spread by attaching
themselves to programs, extensibility features in modern
email programs and web browsers make creating and spreading
viruses much easier.  A recent example is the Melissa Word
macro virus [Pethia99].  It propagates using an infected
Word document contained in an email message.  When a user
opens the infected document, the macro executes
automatically (unless Word macros are disabled).  The macro
then lowers the macro security settings to permit all macros
to run when future documents are opened and propagates
itself by sending infected email messages to addresses found
in the user's Microsoft Outlook address books.  The macro
also infects the standard document template file that is
loaded by default by all Word documents.  If the user opens
another Word document, that document will be mailed along
with the virus to addresses in the user's address books.

The most common virus countermeasures are virus detection
programs such as McAfee VirusScan [McAfee99] and Symantec
Norton AntiVirus [Symantec98].  Nearly every new PC comes
with virus detection software installed.  Most virus
detectors scan files for signatures matching a database of
known viruses.  Commercial products for detecting viruses
recognize tens of thousands of known viruses, and their
vendors employ large staffs to identify new viruses.

The problem with this approach is that it depends on
recognizing a known virus, so it offers no protection
against new viruses.  Because viruses like the Melissa macro
virus can spread remarkably quickly over the Internet, they
can do considerable damage before they are identified and
virus detection databases can be updated.  The damage
inflicted by Melissa was limited to propagating itself and
sending possibly confidential files to known addresses.  A
terrorist motivated to cause as much damage as possible
could fairly easily create a variant of Melissa that
inflicts far more harm.

To detect or prevent damage from previously unidentified
viruses requires an approach that does not depend on
recognizing a known sequence of instructions.  Some
commercial virus detection products include heuristics for
identifying likely viruses based on static properties of the
code or dynamic properties of an execution [Symantec99].
These approaches lead to an arms race between virus creators
and virus detectors, as virus creators go to greater lengths
to make their viruses hard to detect.  Although heuristic
detection techniques show some promise, it is unlikely that
they will ever be able to correctly distinguish all viruses
from legitimate programs.

A different approach is to limit the damage viruses can
cause and their ability to propagate by observing and
constraining program behavior.  For example, the damage done
by macro viruses could be limited by enforcing a policy on
Microsoft Word executions.  We would want to enforce
different policies on Word executions depending on whether
they were started to read a document embedded in an email
message or web page, or started to edit a trusted document.
When Word is used to edit a local document, perhaps a policy
that prohibits any network transmission would be adequate.
For documents from untrustworthy sources, a reasonable
policy would require explicit permission from the user
before Word transmits anything over the Internet, reads
sensitive files, alters the registry, or modifies the
standard document templates.

     Trojan horses
A Trojan horse is an apparently useful program that also
does some things the user considers undesirable.  There have
been many instances where an attacker has distributed a
deliberately malicious program in the guise of a useful one.
For example, someone distributed a version of linux-util
that contained a login program that would allow unauthorized
users to execute arbitrary commands [CERT99b].

In addition, there are programs a user may consider
malicious even if the author did not intend to produce a
malicious attack.  For example, an early version of the
Microsoft Network client would read and transmit the user's
directory structure [Risks95].  While most users would be
unaware that this is occurring, and would not be overtly
damaged by it (other than losing bandwidth that could have
been used for transmitting useful data), many would consider
it a privacy violation.

Countermeasures for Trojan horses are similar to those for
viruses, except that more precise policies may be needed.
Although it would be difficult to monitor the information
sent over the network by the Microsoft Network client, it
would be possible to detect suspicious transmissions and
alert the user.  A more reasonable policy would ignore the
actual transmitted data but place restrictions on which
files, directories and registry entries could be examined,
thereby limiting the information available to the program.

     Faulty programs
Program bugs pose two different kinds of security threats ---
an attacker may deliberately exploit them or they may
accidentally cause harm directly.  The security advisories
recorded by CERT [CERT99a] are rife with examples of buggy
programs leading to exploitable security vulnerabilities.
Of the 71 advisories posted between January 1996 and May
1999, 60 are directly attributable to specific program bugs
(of these, 13 are the direct result of buffer overflows).  A
particularly vulnerable program is sendmail.  Attackers have
exploited various bugs in sendmail to gain root access
[CERT96a, CERT96b], execute programs with group permissions
of another user [CERT96c], and to execute arbitrary commands
with root privileges [CERT97].

Other program bugs cause harm unintentionally.  One
notorious example is the Therac-25, a device for
administering radiation to cancer patients [Leveson93].
Because of software bugs, it would occasionally administer a
lethal dose of radiation and several patients died as a
result.  Although the system software had ad hoc safety
checks, they were obviously not sufficient.3  Because they
were ad hoc, operators and doctors could not examine them
and decide if the device was trustworthy.

The best way to obtain protection from exploitable or
harmful program bugs would be to produce bug-free programs.
Despite progress in software development and validation
techniques, it is inconceivable that this will be
accomplished in the foreseeable future.  Since programs will
inevitably contain bugs, code safety systems should be used
to limit the damage resulting from buggy programs.

As with Trojan horses, the expected behavior of the program
is known so it is reasonable to enforce a precise policy
that limits what it can do.  The difference is that the
software vendor should be an ally in protecting the user
from bugs, unlike a malicious attack.  Security-conscious
software vendors could include policies with their software
distributions or even distribute their software with an
integrated safety policy enforced.  Reputable vendors should
be motivated to protect their users from damaging bugs and
might be expected to devote some effort towards producing a
suitable policy.  By separating the policy enforcement
mechanisms from the application, they can have more
confidence that the policy is enforced correctly.  In
addition, publishing an application's safety policy in a
standard, easily understood format would give potential
customers a chance to decide if the application is
trustworthy.

     User mistakes
Perhaps the most common way programs cause harm is
unintentional mistakes by users.  Because of poor interfaces
or ignorance, users may inadvertently destroy valuable data
or unknowingly transmit private information.  One example is
when an unsuspecting user issues the command tar cf * to
create a new directory archive.  This command will replace
the contents of the first file in the directory with an
archive of all other files, destroying whatever happened to
be the first file.  Although the program is behaving
correctly according to its documentation, this is probably
not the behavior the user indented.  A well-designed
interface lessens the risk of harmful user mistakes, but
combining this with a user-selected and independently
enforced policy is a more robust solution.

1.2  Background

Researchers have been working on limiting what programs can
do since the early days of computing.  Early work on
computer security focused on multi-user operating systems
built around a privileged kernel.  The kernel is the only
part of the system that manipulates resources directly.
User programs must call functions in the operating system
kernel to manipulate resources.  The operating system limits
what user programs can do to system resources by exposing a
narrow interface and putting checks in the system calls to
disallow unsafe resource use.  Each application process runs
in a separate address space, enforced by hardware support
for virtual memory.  A process cannot see or modify memory
used by another process since it is not part of its virtual
address space.

The problem with using separate processes to protect memory
is that the cost of creating and maintaining a process is
high, as is the cost of communicating and sharing data
between processes.  Switching between different processes
involves a context switch, which is usually expensive.
Several systems have attempted to provide the isolation
offered by separate processes within a single process by
using software mechanisms.  We use low-level code safety to
refer to security designed to isolate programs and require
that all resource manipulations go through well-defined
interfaces.  It includes the control flow safety, memory
safety, and stack safety needed to prevent programs from
accessing arbitrary memory segments [Kozen98].  There are
several ways to provide low-level code safety.  Approaches
such as the Java byte code verifier and proof-carrying code
techniques statically verify that the necessary properties
are satisfied.  Software fault isolation provides the
necessary guarantees by inserting masking or checking
instructions to limit the targets of jumps and memory
instructions.  Section 7.1 describes work in low-level code
safety.

Although Naccio depends on low-level code safety for the
integrity of its policy enforcement mechanisms, the focus of
this thesis is on policy-directed code safety.  Policy-
directed code safety seeks to enforce different policies on
different executions.  This can be done either by statically
verifying the desired properties always hold, or by
enforcing properties using run-time checking.  Since it is
infeasible to verify most interesting properties on
arbitrary programs, most work has focused on run-time
enforcement.

Most run-time constraint mechanisms, including Naccio, can
be viewed as reference monitors [Lampson71, Anderson72].  A
reference monitor is a system component that enforces
constraints on access and manipulation of a resource.  It
should be invoked whenever the monitored resource
manipulation occurs, and it should be protected from program
code in a way that prevents bypassing or tampering.
Reference monitor systems differ in how the monitors are
invoked.  They could be called explicitly by the operating
system kernel, called by a separate watchdog process, or
integrated directly into program code.  Naccio integrates
reference monitors directly into code, but takes advantage
of system library interfaces to limit the code that must be
altered.

Reference monitors also differ in how checking code is
defined.  Some possibilities include access matrices, finite
automata, or general code.  In a reference monitor security
system, policies are limited by where reference monitor
calls can be placed and what system state they may observe.
There is usually a tradeoff between supporting a large class
of policies and the performance and complexity of the
system.  Naccio security is based on reference monitors that
can be flexibly introduced into programs at different
points.  This allows for a large class of policies to be
enforced, but avoids the overhead necessary to support many
reference monitors when a simple policy is enforced.

One example of a reference monitor is the SecurityManager
used for high-level code safety in the Java virtual machine.
API functions limit what programs can do by using the
SecurityManager class.  It acts as a reference monitor,
enforcing a particular security policy by controlling access
to system calls.  The Java approach limits the policies that
can be enforced since the only places reference monitors can
be invoked are those defined as check methods in the
SecurityManager.  Developers can write a SecurityManager
subclass that performs the desired checking for the given
check methods, but cannot change the places where the API
routines call check methods.   For instance, the constructor
for FileOutputStream calls the SecurityManager.checkWrite
method before opening a file, but the write method that
writes bytes to an open file does not check any
SecurityManager method.  Hence, one can implement an
arbitrary security policy on what files may be written by
writing code for the checkWrite method, but can place no
constraints on the amount of data that may be written to a
file once it has been opened.  Other reference monitor
systems are described in Section 7.3.

1.3  Design Goals

Naccio is intended to be a code safety system suitable for
users in low and medium security environments.  Although its
mechanisms should be reliable enough for use in a high
security environment, users in high-security environments
should avoid untrustworthy code and rely on redundant
mechanisms to avoid disasters.  Further, high security users
are willing to accept more obtrusive code safety mechanisms
than would be acceptable in a less security-critical
environment.  Naccio could be useful as one of the pieces in
a security system for a high-security environment, but would
not be sufficient on its own.

We consider a low security user to be someone who is
unsophisticated in security matters and who uses the
Internet for web browsing and email.  Low security users
occasionally conduct transactions using the Internet and
send and receive business-related email, but are not using
their computer as a critical part of a business.  The vast
majority of current Internet users fit into this category.
Medium security users are somewhat more sophisticated
regarding security and have more to lose if there is a
security breach.  This category includes people running
servers for small businesses and those with a substantial
stake in their on-line reputation.

Some contexts where Naccio should be useful include:

-o-     Executing remote code such as Java applets or ActiveX
	controls in a web browser.  Typical low-security users allow
	their browser to run ActiveX controls with no constraints
	and Java applets with default constraints on what files may
	be read and written and what network connections may be
	made.  This is reasonably acceptable today, since the damage
	an attacker could inflict on a typical user is low.  This is
	changing though, and will continue to worsen as even typical
	users increasingly have a substantial stake in their on-line
	identity and store financial and personal information on
	their computer.  Most medium-security users today configure
	their browser to disable ActiveX controls and either disable
	Java applets or allow them to run but worry that existing
	security measures are inadequate.  While disabling remote
	code addresses the security issues, it sacrifices some of
	the richness of the web.  More precise policies that can
	constrain a greater range of behavior should allow medium-
	security users to comfortably run remote code with
	assurances that it will not exhibit harmful behavior.

-o-     Executing code in mail attachments.  Most modern email
	programs support attachments that may be data files
	containing executable code (such as a Microsoft Word
	document) or a plain executable file.  Two well-publicized
	recent attacks propagated using email attachments --- the
	Melissa macro virus [Pethia99] propagates using a Word
	document attached to an email message and the
	Worm.ExploreZip virus [Cnet99a] propagates by attaching an
	executable file to an email message.  Until these scares
	were widely publicized, typical low-security users would run
	mail attachments without reservations.  Today, most are at
	least aware of the risks and will be reluctant to run
	attachments in messages coming from untrusted sources.
	Since the viruses mentioned above appear to be sent by
	people the user knows, however, this is not sufficient
	protection.  A code safety system could solve the problem by
	allowing attachments to run, but enforce a policy that
	places constraints on their behavior.

-o-     Uploadable code.  Consider an auction site operator who
	wants to support programs submitted by clients that can
	access the server database, do some computation, place bids
	on behalf of its owner, and send messages to its owner.  The
	site operator needs to limit the behavior of the client
	program including what files it can access and what network
	connections it may open, as well as place bounds on the
	server resources it may consume such as network bandwidth
	and database connections.  Support for uploadable code is
	one of the largely unsatisfied promises of the web.  The
	security concerns of site operators is part of the reason so
	few sites support uploadable code.

-o-	Stand-alone applications.  Today a user installing a
	stand-alone application (usually distributed on CD-ROMs or
	as Internet download) either chooses to trust the
	application completely or chooses not to install the
	application.  Security conscious users decide whether an
	application is trustworthy enough to be installed and
	executed based on the reputation of its provider.  Large
	companies are more likely to be trustworthy than individuals
	or small companies.  Today, most applications are shipped in
	forms (e.g., Windows executables) that are not supported by
	most code safety systems.  Efforts to convince program
	vendors to ship programs in a form that is more amenable to
	current code safety systems (e.g., source code or Java byte
	codes) are unlikely to be successful.  Instead, we need code
	safety systems that can efficiently and conveniently enforce
	policies on applications as they are commonly distributed.

-o-     Constraining security-critical programs.  A system
	administrator installing security-critical programs such as
	a remote login shell, an ftp server, a mail server, or a web
	server should be able to enforce specific constraints on
	their behavior.  Although many of these programs do provide
	security configuration options (for example, a web server
	can be configured to allow access only to certain types of
	files), it would be useful to have an independent system
	that enforces these constraints as well as additional
	constraints.  Using a separate code safety tool would have
	the advantage that the system administrator can use the same
	system to configure security constraints on different
	programs and to configure global constraints that apply to
	all programs.  In addition, a code safety system independent
	of an application is not vulnerable to application bugs.
	There may be bugs in the code safety system, but if it is
	simple and extensively used, it is likely to have fewer
	security vulnerabilities than an application-specific
	mechanism.

In order to be useful in these contexts, Naccio
implementations should securely enforce safety policies, and
should be versatile enough to support a wide range of
precise policies encompassing useful constraints on program
behavior.  Those policies should be defined in a way that
makes them easy to define, understand and modify.  It should
be possible to produce Naccio implementations with a
reasonable amount of effort.  Finally, Naccio must be
efficient enough so that even users without critical
security needs will be willing to use it.  These goals are
often conflicting.  Naccio seeks to expand the scope and
precision of policies that can be enforced, as well as
improve the policy-definition mechanisms, without
substantially compromising security or efficiency and
convenience.

1.3.1     Security

Security is an essential property of any code safety system.
A secure code safety system correctly enforces the selected
policy, even in the presence of motivated and knowledgeable
attackers.  Every system has vulnerabilities, but security
systems should strive to eliminate known vulnerabilities and
reduce the likelihood that attackers can find and exploit
unknown vulnerabilities.

While proving a system is secure is generally infeasible for
any non-trivial system, there are design approaches that are
more likely to lead to secure systems.  A simple design is
more likely to be secure than a complex one, since flaws in
a simple design are more likely to be detected and
corrected.  Further, it is more likely that a simple design
can be implemented correctly than a complex design.  A
corollary to the simplicity goal is to have a small trusted
computing base.  If the security-critical part of the system
can be isolated and kept small, it may be possible to verify
its correctness or at least to carefully review the code.

1.3.2     Versatility

To be useful, a code safety system must be able to enforce
useful policies.  The ideal policy would prevent every
behavior the user considers harmful but never issue a
violation for behavior the user considers desirable.  No
such universal policy exists since it is impossible to
perfectly distinguish harmful and desirable behavior.
Indeed, behavior that is desirable for one program (such as
rm deleting a file) would be considered harmful for other
programs.

Supporting a wide range of policies means that policies can
be defined to constrain many different program behaviors.
For example, a system that does not provide any way to
constrain thread creation cannot prevent denial-of-service
attacks that create a huge number of threads.4  A system
that allows constraints on what files may be opened for
reading or writing, but does not support any way of
constraining what may be done with those files after they
are opened must either prohibit writing entirely or allow
attacks that fill up the local disk.

The other aspect of policy precision is the generality of
the policy definition mechanisms.  Some systems support
policy checking based on setting a fixed set of parameters
such as a list of readable and writeable files or an upper
bound on network usage.  This excludes a wide class of
useful policies where the constraints are more dynamic or
depend on other factors.  For example, a useful policy might
constrain what files may be written based on the command
line or the history of user interactions; another policy
might make the network usage bound a function of the number
of keystrokes pressed by the user.

A completely general system would support policy checking
using a universal programming language and with access to
the entire state and history of the program execution.  Such
generality leads to complication in both policy definition
and enforcement, and is probably not necessary for most
practical policies.  Instead, successful systems will make
compromises based on providing sufficient generality to
define most useful policies but enough limitations to make
efficient and reliable enforcement feasible.

1.3.3     Ease of Use

A code safety system is useful only if it can enforce
policies that place useful constraints on program behavior.
In addition, there must be a way to define those policies.
If it is too difficult or cumbersome to define policies,
only predefined policies will be available to typical users.
Only the most sophisticated experts will be able to create
new policies, and obtaining a customized policy will be an
expensive and time-consuming proposition.

Defining a policy requires good understanding of security
requirements, but should not require extensive understanding
of the execution platform.  A policy definition mechanism
that defines policies in terms of system calls on a
particular platform can only be used by an elite group of
platform experts.  It is easy for even experts to forget
about obscure system calls that can be used to manipulate
resources leading to exploitable vulnerabilities.  Naccio
seeks to simplify policy definition by expressing policies
in terms of manipulations of abstract resources that are not
tied to a particular platform implementation, but correspond
to things users understand like files and network
connections.

1.3.4     Ease of Implementation

Since we hope that many implementations of Naccio will be
developed, it is important that a Naccio implementation for
a new platform can be produced with a reasonable amount of
effort.  Although some work will inevitably be required to
support a new platform, Naccio's design should maximize
reusability across platforms.  It should also be clear what
needs to be done to produce a Naccio implementation for a
new platform, once the relevant properties of that platform
are understood.

1.3.5     Efficiency

The normal behavior of a code safety system is to do nothing
noticeable to the user.  A code safety system should be
apparent only in the unusual situation where a program is
about to violate the policy.  This means the time and effort
required to prepare a program to run with a selected policy
enforced should be minimal.  Most users will not even select
the policy themselves, but rely on predefined policy
settings established by their operating system or system
administrator.

A code safety mechanism should also be transparent when a
program runs, unless the policy is violated.  It should not
unduly affect the performance of the execution.  The costs
of enforcing a policy should be directly related to the
complexity and ubiquity of the policy.  It is reasonable
that there be a significant overhead associated with
enforcing a policy that monitors every byte written to
files, but unreasonable for there to be any noticeable
overhead for a policy that limits what directories can be
read.  Typical access control policies should be enforced
with negligible overhead.

1.4  Contributions

This thesis presents a novel solution to the problem of
constraining the behavior of program executions.  We focus
on addressing the limited class of policies supported by
traditional code safety systems and the inadequate
mechanisms they provide for defining policies.

Several other recent research projects have also attempted
to expand the class of policies that a code safety system
can enforce, most notably Ariel [Pandey98] and SASI
[Erlingsson99].  Like Naccio, Ariel and SASI enforce
policies by transforming programs.  Naccio, Ariel and SASI
can all enforce similar classes of policies.  The key
differences between Naccio and these and other projects are:

  -o-   Naccio is the first code safety system that defines
	safety policies in terms of abstract resource manipulations.
	This makes safety policies easier to write and understand,
	and means the same policy can be enforced on different
	platforms.

  -o-   Naccio is the first code safety system to use a two-
	stage process where policy compilation is separate from
	program transformation.  This allows time-consuming
	optimizations that improve execution performance to be
	performed at policy compilation time, while allowing a
	policy to be enforced on an execution of a new program with
	low overhead.

Section 7.3 describes Ariel and SASI in more detail and
clarifies the subtle differences in the classes of policies
they can define.

Much was learned by building two Naccio prototype
implementations and using them to define policies and
enforce them on executions.  Some specific contributions
resulting from this experience include:
  -o-   We showed that it is possible to obtain the benefits of
	a large class of enforceable policies without sacrificing
	run-time performance when simple policies are enforced.
  -o-   We devised a specialization of dead code elimination
	that can be used to eliminate unnecessary checking code in
	code safety systems.  This helps achieve our goal of only
	paying overhead for security checking when useful checking
	is being done.
  -o-   We gained an understanding of the tradeoffs involved in
	enforcing policies at different levels (for example, at the
	level of system calls or the level of machine instructions).
	The Naccio architecture provides a clear framework for
	understanding what is lost or gained by selecting a
	particular level where policies are enforced.
  -o-   We clarified what properties must be guaranteed to
	ensure the integrity of wrapper-based checking mechanisms
	and designed mechanisms that provide these guarantees on the
	JavaVM and Win32 platforms.
  -o-   We introduced language features for creating groups of
	related resource operations.  These groups can be used to
	define safety policies more easily and robustly.
  -o-   We introduced new mechanisms for combining safety
	properties based on intersection and weakening.  These
	mechanisms are sufficiently powerful to enable easy
	expression of a wide class of policies, but simple enough to
	be readily understood and efficiently implemented.
  -o-   We developed a framework that can be reused to produce
	Naccio implementations for additional platforms with reduced
	effort.

Although the policy enforcement architecture is designed
with the policy definition mechanisms in mind, they are
separable.  It would be reasonable to use different
enforcement mechanisms to enforce policies defined using
Naccio's definition mechanisms.  Conversely, Naccio's
enforcement architecture could be used to enforce policies
defined in some other way.

1.5  Overview of Thesis

Chapter 2 introduces the Naccio architecture, describes its
components and presents an example that shows how a policy
is defined, compiled and enforced on a program execution.
Chapter 3 describes how safety policies are defined.
Chapter 4 describes how a platform is described in terms of
its resource manipulations and how the platform interface
can be altered to expand the class of policies that can be
defined.

The next two chapters describe issues relating to enforcing
policies in general as well as implementation issues
involved in the two prototype implementations.  Chapter 5
discusses what is done to compile a policy irrespective of
the target application.  Chapter 6 explains what is done to
enforce a policy on a particular program execution.

Chapter 7 describes related work in code safety and program
transformation.  Chapter 8 evaluates Naccio's potential and
examines vulnerabilities in the architecture generally, and
in the prototype implementations specifically.  Chapter 9
suggests future work and Chapter 10 summarizes the thesis
and draws conclusions.
                                                            
                                                            
               This Software is not designed or intended for
                     use in on-line control of aircraft, air
                    traffic, aircraft navigation or aircraft
                           communications; or in the design,
               construction, operation or maintenance of any
                 nuclear facility. Licensee warrants that it
               will not use or redistribute the Software for
                                              such purposes.
                           Sun JDK Noncommercial Use License




Chapter 2
Naccio Architecture5



Naccio is a system architecture for defining safety policies
and enforcing those policies on executions.  Conceptually,
Naccio takes a program and a description of a safety policy,
and produces a new program that behaves like the original
program except that it is constrained by the safety policy.
The Naccio architecture includes platform-independent
languages for describing resources, general languages for
specifying a safety policy in terms of constraints on those
resources, and a family of platform-dependent languages for
describing system calls in terms of how they manipulate
resources.  It also provides a framework for implementing
policy enforcement mechanisms by transforming programs.
This chapter provides an overview of the architecture.
Chapters 3 and 4 describe how safety policies are defined.
Chapters 5 and 6 describe issues involved in implementing
the architecture and relate experience from building the two
prototype implementations.

2.1  Overview

Suppose we wish to enforce a policy that limits the total
number of bytes an execution may write to files.  An
implementation will need to maintain a state variable that
keeps track of the total number of bytes written so far.
Before every operation that writes to a file, we need to
check that the limit will not be exceeded.  One way to
enforce such a property would be to rewrite the system
libraries to maintain the necessary state and do the
required checking.  This would require access to the source
code of the system libraries, and we would need to rewrite
them each time we wanted to enforce a different policy.  If
the operating system were upgraded, the policy would need to
be rewritten.

Instead, we could write wrapper functions that perform the
necessary checks and then call the original system
functions.  To enforce the policy, we would modify target
programs to call the wrapper functions instead of the
protected system calls.  Though wrappers are a reasonable
implementation technique, they are not an appropriate way to
describe safety policies since creating or understanding
them requires intimate knowledge of the underlying system.
To implement a policy that places a limit on the total
number of bytes that may be written to files, one would need
to identify and understand every system call that may write
to a file.  For even a supposedly simple platform like the
Java API, this involves dozens of different routines.
Changing the policy would require editing the wrappers, and
there would be no way to use the same policy on other
platforms.

Naccio's solution is to express safety policies at a more
abstract level and to provide a tool that compiles these
policies into the wrappers needed to enforce a policy on a
particular platform.  Safety policies are defined by
associating checking code with abstract resource
manipulations.  A platform is characterized by how its
system calls manipulate resources.

Figure 1 shows the Naccio system architecture.  It is
divided into a policy compiler and a program transformer.
The policy compiler is run once per policy-platform pair.
The policy compiler takes a definition of a resource use
policy and a platform interface that describe an execution
platform and produces a policy-enforcing platform library
and a policy description file that encodes the
transformations the program transformed must do to produce a
program altered to enforce the policy.  Since policy
compilation is a relatively infrequent task, we trade off
execution time of the policy compiler to make program
transformation fast and to reduce the run-time overhead
associated with safety checks.  Once a policy has been
compiled, the resulting policy-enforcing platform library
and policy description file can be reused for each
application on which we want to enforce the policy.  Section
2.2 discusses the inputs and outputs of the policy compiler,
and Chapter 5 provides details on how the policy compiler
works.

The program transformer is run for each application-policy
pair.  It reads the policy description file produced by the
policy compiler to determine what transformations need to be
done to enforce the policy on an execution, and rewrites the
program accordingly.  The transformations typically include
replacing calls to a platform library with calls to a policy-
enforcing platform library produced by the policy compiler.
In addition, the program transformer must ensure the
necessary low-level code safety properties to prevent
malicious programs from being able to tamper with the safety
checking.  Once the transformed program has been produced,
it can be run normally and the policy will be enforced on
the resulting execution.  Section 2.3 discusses what the
program transformer must do to enforce a policy, and Chapter
6 provides details on how this is done.

	
	< Figure not available in plain text. >
                              
               Figure 1.  Naccio Architecture.
       The left side of the figure depicts what a
       policy author does to generate a new policy.
       The right side shows what happens the first
       time a user elects to execute a given program
       enforcing that policy.  The program
       transformer is run with an argument that
       identifies the policy description file to use.

An implementation of Naccio is characterized by the kind of
program it transforms; the format and content of the
platform libraries it uses; and the level of its platform
interface, which determines the level at which it must
transform the platform libraries and programs.  We have
built two Naccio prototype implementations: Naccio/JavaVM
that enforces safety policies on JavaVM classes and
Naccio/Win32 that enforces safety policies on Win32
executables.  Although the design is intended to be general
enough to apply to most modern platforms, the details and
results in this thesis are derived from experience with
these prototype implementations.

2.2  Policy Compiler

The policy compiler takes files describing a safety policy
and an execution platform, and produces what is needed to
enforce the policy.  The input files consist of resource
descriptions that provide a way to refer to resource
manipulations abstractly; a platform interface that
describes a particular execution platform in terms of those
resource descriptions; a platform library, the unaltered
code provided by the platform implementation (for example
the Java API classes or Win32 system DLLs), and a resource
use policy that specifies the constraints on program
behavior to be enforced.  For most policies, the resource
descriptions and platform interface are treated as a fixed
part of the implementation and the policy author writes a
resource use policy.

A resource description defines a resource object and a list
of resource operations that identify different ways of
manipulating that resource object.  For example, a resource
description for a file system has a resource operation
corresponding to writing bytes to a file.  A resource use
policy defines a safety policy by attaching checking code to
these resource operations.  Safety policies can be written
and understood by looking solely at the resource
descriptions and resource use policy.  Naccio defines a
standard set of resources that must be provided by any
Naccio implementation.  Policies defined in terms of those
resources are portable and can be enforced without any extra
effort on any platform for which a Naccio implementation is
available.  Policies defined in terms of the standard
resources are known as standard safety polices.  A challenge
in designing Naccio is to choose a set of standard resource
descriptions that can be used to define most typical safety
policies, but that correspond precisely to the way actual
resources are manipulated on different platforms.  Chapter 3
describes how safety policies are defined, summarizes the
contents of the standard resource library, and discusses the
range of policies that may be expressed as standard safety
policies.

A platform interface provides an operation specification of
an execution platform in terms of a set of resource
descriptions.  The platform interface is a collection of
wrappers that map concrete operations in a particular
platform to the abstract resource manipulations described by
the resource descriptions.  The platform interface hides
platform details from a policy author who need only look at
the resource descriptions.  A platform interface may be
defined at different levels ranging from hardware traps to
machine instructions to the system API to an application-
specific library.  For the most part, we focus on platform
interfaces at the level of the system API since it is
usually a well-defined interface and it provides a
convenient place to interpose checking code.  Platform
interfaces at lower levels would be necessary to support
policies that involve resource manipulations that are not
visible through API calls.  Platform interfaces at higher
levels may be useful if we wish to support policies that
apply to library or application level resources.  If a
policy author wishes to express a policy that cannot be
defined in terms of the available resource descriptions, new
resource operations can be defined by altering the platform
interface.  Chapter 4 describes the platform interface, and
illustrates how the platform interface can be altered to
define safety policies that cannot be expressed using the
standard resource descriptions.

The policy compiler analyzes the resource use policy,
resource descriptions and platform interface and produces a
policy-enforcing platform library.  If the platform
interface is at the level of a system API, the policy
compiler may also read and analyze the platform library
object code, such as the Win32 API DLLs or the Java API
classes.  This is used to produce a new version of the
platform library that includes checking code necessary to
enforce the policy but otherwise behaves identically to the
original platform library.

The policy-enforcing platform library makes calls to
resource implementations, routines that correspond to the
resource operations.  The resource implementations do
checking as directed by the resource use policy.  The
resource use policy defines checking code associated with
resource operations.  The policy compiler translates the
code from the resource use policy and turns these resource
operations into routines that can be called by the policy-
enforcing platform library.  Much of the work of the policy
compiler is platform-independent.  It parses the resource
descriptions and resource use policy into intermediate
languages and weaves the checking code into the appropriate
resource operations.  The resource operations are then
implemented using a platform-specific back end that
translates the intermediate language into executable code
that performs the necessary checking.

The platform interface specifies how system calls need to be
wrapped to call the appropriate resource operations.  If run-
time performance were not a concern, Naccio could generate
the platform interface wrappers once and switch which
resource implementations are used to enforce different
policies.  However, this would mean the overhead of going
through a wrapper for a system call that manipulates
constrainable resources would always be required regardless
of whether or not the policy in effect constrains those
resource manipulations.  Instead, the policy compiler
generates a new wrapped platform library for every policy.
This means wrappers need only be generated for system calls
that manipulate constrained resources.  Generating a policy-
specific version of the platform interface wrappers also
allows for other optimizations to be performed, as described
in Section 5.5.

The other output of the policy compiler is a policy
description file that contains a compact representation of
the transformations the program transformer must carry out
to enforce the policy.  The policy description file
identifies the location of the policy-enforcing platform
library so the application transformer can make the
necessary changes.  In addition, it may include rules to
rename routines to call wrappers in place of system calls.
This may be necessary in certain cases (such as wrapping
native methods in Java) where the policy compiler cannot
replace the routine in the policy-enforcing library.  Other
rules list resource operations that must be called at the
beginning of execution (initializers) and resource
operations must be called immediately before execution
completes (terminators).

2.3  Program Transformer

The program transformer is run when a user elects to enforce
a particular policy on an application for the first time.
In a typical deployment, a web browser or application
installer would run it transparently before a new program is
executed based on a user's security settings. The program
transformer reads a policy description file and a target
program and performs the directed transformations to produce
a version of the program that is guaranteed to satisfy the
safety policy.  For each program and selected policy, we
need to run the program transformer once.  Afterwards, the
resulting program can be executed normally.  The type of
program transformed depends on the particular Naccio
implementation.  It could be source code or object code,
although implementations of Naccio that support object code
are more likely to be useful since many vendors are
unwilling to ship source code.  The prototype
implementations handle programs that are JavaVM classes and
Win32 executables.

The program transformer makes two main changes to the
program: it replaces the standard platform library with the
policy-enforcing platform library produced by the policy
compiler, and it modifies the program to ensure that the
resulting program satisfies the low-level code safety
properties necessary to prevent malicious programs from
circumventing or altering the policy checking mechanisms.
Both changes are platform-dependent, and as a result not
much of the program transformer can be reused across
different Naccio implementations.  In addition, if the
policy requires calls to initializers or terminators, the
program transformer inserts these calls.

Switching the library is usually fairly simple on most
modern platforms in which the platform library is linked
dynamically.  For Naccio/JavaVM it involves changing the
CLASSPATH or replacing class names; for Naccio/Win32 it
involves replacing file names in the import table.
Guaranteeing the integrity of policy checks is more
complicated.  Naccio implementations must prevent programs
from writing to storage or code used in safety checking or
manipulating resources without going through the policy-
enforcing platform library.  Useful techniques for doing
this include statically verifying that the necessary
properties hold, performing low-level transformations on the
application code to guarantee the necessary properties, and
using platform interface wrappers so that the necessary
properties are enforced by all policies.  Section 6.2
discusses what must be protected and how this is done in
Naccio implementations.

Figure 2 shows a sample wrapped system call sequence in a
transformed program.  Instead of calling the system call in
the platform library directly, the transformed program calls
the wrapped version of the system call in the policy-
enforcing platform library that was produced by the policy
compiler.  This routine calls resource operations as
directed by the platform interface.  It may also need to do
some bookkeeping to determine the correct arguments to pass
to the resource operations.  For the example, the wrapper
for WriteFile must convert the file handle into an abstract
resource object that identifies the corresponding file.  The
resource operations implement the checking specified by the
resource use policy.  If the policy would be violated by the
system call, the resource implementation calls a Naccio
library routine that reports the policy violation and gives
the user the option to terminate or alter the execution.  If
not, the original system call in the platform library is 
called and the execution continues normally.  Additional 
resource operations may be called after the system call returns.  
Depending on the Naccio implementation, the wrapper code may 
be embedded directly in the policy-enforcing platform library or 
kept as a separate library.

	  < Figure not available in plain text. >

          Figure 2.  Wrapped system call sequence.


2.4  Walkthrough Example

This section walks through all the steps necessary to define
and enforce a policy.  It is not intended to be
comprehensive, but to give the reader an idea of how all the
pieces fit together.  Chapters 3 through 6 describe each
step in more detail.  For this example, we consider using
Naccio/JavaVM to enforce the LimitBytesWritten policy, which
sets a limit of one million on the number of bytes that may
be written to the file system on an execution of an
application comprised of a set of Java class files.  These
steps would be substantially similar for Naccio/Win32 and
implementations of Naccio for other platforms, but for
simplicity this example is limited to Naccio/JavaVM.

This policy is expressed formally using Naccio's policy
definition languages.  We maintain a state variable that
keeps track of the number of bytes written to the file
system.  We do this by declaring a new field named
bytes_written that is associated with the RFileSystem
resource object that represents the file system.  This
resource object is global over an execution, so the value of
RFileSystem.bytes_written is maintained across the
execution.  This value needs to be incremented every time
bytes are written to the file systems.  The
RFileSystem.postWrite resource operation corresponds to the
point immediately after bytes were written to the file
system, and we can maintain the value by attaching code that
increments bytes_written to this resource operation.  The
bytes_written field declaration and updating code are
encapsulated in a state block that can be reused by other
safety policies.

To enforce the limit, we need to check that the limit will
not be exceeded before allowing a write to proceed.  We do
this by attaching checking code to the RFileSystem.preWrite
resource operation that corresponds to the point immediately
before bytes will be written to the file system.  This
checking code compares the sum of the number of bytes
already written (as recorded in the
RFileSystem.bytes_written state variable) and the number of
bytes about to be written to the limit enforced by the
policy.  If the limit would be exceeded, it issues a
violation and gives the user an opportunity to terminate the
execution.  The code used to define this policy is shown in
Figure 6 in Section 3.2.

The policy must be compiled before it can be enforced on an
application execution.  To compile a policy, we need an
operation specification of the execution platform known as a
platform interface.  The platform interface describes
concrete events in terms of the abstract resource
descriptions used to define the policy.  Naccio/JavaVM uses
a platform interface at the level of the Java API (the java.
classes).  The Java API platform interface describes each
method in the Java API by calling resource operations at the
execution points defined by the resource descriptions.  For
example, the description of the RFileSystem.preWrite
operation documents that it should be called before every
write to the file system with a parameter that gives an
upper bound on the number of bytes about to be written.  The
platform interface wrapper for the
java.io.FileOutputStream.write(byte[]) method indicates that
RFileSystem.preWrite should be called before the write
method is called, and RFileSystem.postWrite should be called
after the write method returns.  The policy compiler
produces a new version of the java.io.FileOutputStream class
that replaces the write method with a wrapper that calls the
resource operations as described by the platform interface
around the original method.  The Naccio/JavaVM platform
interface wrapper for the java.io.FileOutputStream class is
shown in Figure 11 and discussed in Section 4.2.

The policy compiler also generates implementations
corresponding to the abstract resource operations that are
called by the generated wrapper classes.  Naccio/JavaVM
implements each resource using a Java class with a method
that corresponds to each resource operation.  Code from the
resource use policy is woven into the resource
implementations and translated to Java code.  Section 5.3
explains how the policy compiler generates a resource
implementation class.

A policy author or system administrator runs the policy
compiler, and its output can be used to enforce the policy
on any JavaVM program.  The generated wrapper classes and
resource implementations are stored in a protected directory
and the policy compiler generates a policy description file
that encodes the transformations needed to enforce the
policy on an execution.  When a user elects to enforce the
policy on a program execution, the application classes are
transformed according to the rules in the policy description
file.  For Naccio/JavaVM, this can involve simply setting
the CLASSPATH so that the generated wrapper classes are
found before the standard Java API classes.  After this has
been done, the application can be executed normally with the
safety policy enforced on its execution.  Chapter 6
describes the program transformer.

Figure 3 shows what happens at run-time to enforce the
LimitBytesWritten policy on an application that creates a
java.io.FileOutputStream and writes an array of bytes to it.
The original FileOutputStream class is replaced with a
policy-enforcing wrapper version of the class, shown in the
figure as lbw.FileOutputStream.  The constructor for this
class constructs an RFile object that is an abstract
resource corresponding to the file associated with this
output stream.  This object is stored in an instance
variable of the lbw.FileOutputStream object, and will be
passed to resource operations like RFileSystem.preWrite.
After constructing this object, the original constructor
executes normally and stores the RFile object in a new
instance variable.  Unlike the RFile object, the RFileSystem
is a global resource so there is only one RFileSystem object
for the entire execution.  When the execution calls
java.io.FileOutputStream.write(byte[]), the wrapper for this
method will call the resource operation
RFileSystem.preWrite, passing in the RFile object associated
with this FileOutputStream and the size of the array.  The
RFileSystem.preWrite implementation contains the checking
code from the policy, and will issue a violation if the
policy would be violated by the write method call.
Otherwise, it returns and the original write method is
executed.  After it completes, RFileSystem.postWrite is
called.  This method contains the code that increments
bytes_written.


	< Figure not available in plain text. >

        Figure 3.  Interaction diagram for enforcing
                     LimitBytesWritten.
     For an explanation of the interaction diagram notation
     see [Gamma95].  The gray objects are classes modified
     by Naccio.  The black objects are classes generated by
     Naccio.








Chapter 3
Defining Safety Policies



This chapter describes how Naccio is used to define safety
policies.  For standard policies, we consider the resource
descriptions and platform interface to be a fixed part of
the system and express a policy only in terms of resource
use constraints.  Standard polices are portable across
Naccio implementation platforms.  The standard resources are
chosen so that many useful safety policies can be defined as
standard safety policies.  This includes policies that place
access constraints on system resources such as reading and
writing files and opening network connections, and policies
that place limits on consumption such as the number of files
that may be touched or the number of bytes that may be
written to the file system.  This chapter discusses resource
descriptions, specifying safety policies that constraint
resource manipulations, the contents of the standard
resource library and the limits on expressiveness for
standard safety policies.  In the next chapter, we discuss
how a platform interface is used to specify a platform in
terms of how it manipulates resources and consider policies
that can be expressed by changing the platform interface.

3.1  Resource Descriptions

A program runs by executing a sequence of instructions.
Those instructions modify the state of the processor and may
affect devices attached to the machine such as its hard
drive, network connection and display.  We can view
everything a program can manipulate as a resource.  A safety
policy imposes constraints on how a program manipulates
resources.  In order to define a safety policy, we need a
precise way of referring to resource manipulations.

Resource descriptions provide a way to identify resources
and describe ways they are manipulated.  Examples of
resources include files, network connections, threads and
displays; examples of manipulations are writing ten bytes to
a file, opening a network connection to port 80 on
naccio.lcs.mit.edu, increasing the priority of a thread, or
opening a window.  Resource descriptions are written in a
platform-independent language, but they may describe
platform-specific resources such as the Windows registry.
Naccio includes a set of standard resource descriptions that
encompass the resource manipulations that are common on
nearly all platforms and are relevant for many security
policies.

We describe resources by listing their operations.  Typical
resource descriptions have no state or implementation.  They
are merely hooks for use in defining safety policies.
Resource descriptions may use primitive types including int,
float and immutable Strings.  These types are defined by
Naccio to have the expected semantics.  The meaning of a
resource operation is indicated by informal documentation.
This documentation should be clear and precise to the policy
author, but is not sufficiently formal to be processed by a
machine.

Policy authors read resource descriptions, but do not need
to modify them for typical policies.  A policy is expressed
by associating checking code with resource operations.  The
essential promise is that a transformed program will invoke
the related resource operation with the correct arguments
whenever a particular event occurs. It is up to the policy
compiler and platform interface to ensure that this is the
case.

Figure 4 shows two resource descriptions related to the file
system.  It declares the RFileSystem resource object that
represents to the file system as a whole, and the RFile
resource object that identifies a single file or directory.
The RFileSystem resource has operations that correspond to
manipulating files and directories.  The RFile resource only
contains a constructor for creating a resource object that
identifies a particular file.  The global modifier indicates
that only one RFileSystem instance exists for an execution6.
Resources declared without a global modifier are associated
with a particular run-time object.  Most of the RFileSystem
operations take an RFile parameter to identify a particular
file.  Dividing a resource into a global resource for the
actual manipulations and instance resources for identifying
resources is a common paradigm.  This division makes it easy
to write policies that constrain system-wide resource use
(for example, the total number of files that are opened),
but provides an abstract way to identify specific objects
such as files.

3.1.1     Resource Operations

The body of a resource description is a list of operations
and groups.  Each operation corresponds to a particular way
of manipulating a resource.  For example, the openRead
operation corresponds to opening a particular file for
reading.  Its documentation prescribes that openRead is
called before a file is opened for reading.  It takes a
parameter of type RFile that represents the file being
opened.

The documentation associated with each resource operation
must be precise enough so that policy authors can write
policies that behave as expected.  However, it should not be
over specified in ways that prevent it from being applicable
on different platforms.  For example, what it means to open
a file is a platform-specific notion.  The essence of the
open operations is given by the documentation for the read
and write operations that indicate the relevant open
operation must be called first.  Platform-specific
documentation may be necessary in some cases to clarify what
resource operations mean.  Given reasonable choices,
however, policies can be reused across platforms with their
intended meaning.

Resource manipulations may be split into more than one
resource operation.  For example, reading is split into the
preRead and postRead operations.  This division allows more
precise safety policies to be expressed.  Pre-operations
allow necessary safety checks to be performed before the
action takes place, while post-operations can be used to
maintain state and perform additional checks after the
action has been completed and more information is available.
For this example, the actual number of bytes read may not be
known until after the system call that does the read has
completed.


global resource RFileSystem
  operations
	initialize ()  
                Called when execution starts.
	terminate ()                    
		Called immediately before execution ends.
	openRead (file: RFile)   
		Called before file is opened for reading.
	openAppend (file: RFile)               
		Called before file is opened for appending.
	openCreate (file: RFile) 
		Called before file is created for writing.  
		At this point in the execution, file must 
		not exist.
	openOverwrite (file: RFile) 
		Called before file is opened for writing.
		At this point in the execution, file exists.

	close (file: RFile)         
		Called before file is closed.
	preDelete (file: RFile) 
                Called before file is deleted.
	postDelete (file: RFile)          
		Called after file is deleted.
	renameNew (file: RFile, newfile: RFile)
		Called before file is renamed to new file newfile.  At
		this point in the exection, newfile must not exist.
	renameReplace (file: RFile, newfile: RFile)     
		Called before file is renamed to existing file newfile.
	makeDirectory (file: RFile)                    
		Called before creating new directory file.
	preWrite (file: RFile, n: int)
		Called before up to n bytes are written to file; file must
		have previously been passed to openCreate, openOverwrite
		or openAppend.
	postWrite (file: RFile, n: int)                  
		Called after exactly n bytes were written to file.
	preRead (file: RFile, n: int)
		Called before up to n bytes are read from file; file must
		have previously been passed to openRead.
	postRead (file: RFile, n: int)                   
		Called after exactly n bytes were read from file.

	 observeExists (file: RFile)                 
		Called before revealing if file exists.
	observeWriteable (file: RFile)                   
		Called before revealing if file is writeable.
	observeCreationTime (file: RFile)                
		Called before revealing creation time of file.
	observeList (file: RFile)                      
		Called before revealing files in directory file.
        ... // other similar observe operations elided

	setCreationTime (file: RFile)               Called before
		changing creation time of file.
	... // other similar set operations elided

 group modifyExistingFile (file: RFile)
Called before contents of any existing file are modified.
   openOverwrite, openAppend, preDelete,
   renameNew (file: RFile, newfile: RFile):
modifyExistingFile (file),
   renameReplace (file: RFile, newfile: RFile):
modifyExistingFile (file),
   renameReplace (file: RFile, newfile: RFile):
modifyExistingFile (newfile);

 group modifyFile (file: RFile)                   Called
before any file is altered or created.
   modifyExistingFile, openCreate,
   renameNew (file: RFile, newfile: RFile): modifyFile
(newfile);
 group observeProperty (file: RFile)              Called
before any property of file is revealed.
   observeExists, observeWriteable, observeCreationTime, ...;
 ... // Other groups elided.

resource RFile
 operations
      RFile (pathname: String)               Constructs
object corresponding to pathname.  Pathname
                            is a canonical string that
identifies a file.

              Figure 4.  File System Resources.

The RFile resource has only one operation, a constructor.
It takes a string parameter that identifies a file in some
platform-dependent way.  The RFile resource objects have no
state or operations provided to obtain information about
what actual file a particular RFile object represents.
Policies can add the necessary state and operations to
determine properties of an RFile.  Section 3.2.1 illustrates
how this is done.

Two special resource operations are not associated with
resource manipulations but represent the beginning and
ending of executions.  The initialize operation is called at
the beginning of execution, before any program-directed file
manipulation is done (file manipulations done by system
initialization code may occur before initialize is called).
The terminate operation is called after all program-directed
file manipulations have completed.  Most global resources
provide initialize and terminate operations.  They provide
useful places to attach checking code or to initialize state
associated with checking.

3.1.2     Resource Groups

Resource operations may also be grouped to make it easier to
write safety policies.  A resource group is a set of
resource operations and other resource groups that
correspond to similar manipulations.  Grouping operations
makes it easier to define policies that do not depend on
specific manipulations.  For example, the observeProperty
group encompasses all resource operations that correspond to
observing properties of a file.  It includes the
observeExists operation that is called before revealing if
the given file exists and several other operations
associated with observing properties of a file.  Since some
policies need to distinguish between observing whether a
file exists and observing the size of a file, the
RFileSystem resource description should have separate
operations corresponding to each manipulation.  Since many
policies do not need to distinguish between the different
ways of observing file properties, it is also useful to
define a group that encompasses all the file observation
operations.

A resource group is defined by listing the operations and
groups it contains.  All members in a resource group must
map to the parameters of the group.  The mapping is given by
a function-call like syntax that calls the group name.
Conceptually, the resource operation calls the group in the
way given by the function call.  For example, in the
modifyExistingFile group list we use
    
        renameNew (file: RFile, newfile: RFile) :
    modifyExistingFile (file),

to map rename, which takes two parameters, into the
modifyExistingFile group, which takes a single RFile
parameter.  Since the only existing file modified by
renameNew is the file corresponding to its first parameter,
the group mapping passes this parameter to
modifyExistingFile.  For renameReplace, both the file and
newfile already exist so two existing files are modified by
the corresponding resource manipulation.  The group
definition for modifyExistingFile lists renameReplace twice
with different mappings corresponding to each file
modification.

If the group parameters and the member parameters match
exactly, listing the operation name assumes the implicit
mapping where the parameters correspond directly.  For
example, the observeExists resource operation and
observeFile resource group both take one parameter of type
RFile, so listing observeExists is sufficient.

3.2  Safety Properties

A safety property attaches checking code to resource
operations or groups.  The simplest safety property
specifies that a particular resource manipulation is not
permitted.  For example,

    property NoDeleting {
     check RFileSystem.preDelete (file: RFile) {
      violation (``File deletion prohibited.'');
     }
    }

defines a property that issues a violation before an
application would delete a file.  The documentation given in
the RFileSystem resource description shown in Figure 4
indicates that the preDelete operation is called before a
file is deleted.  The body of the check clause calls the
violation function provided by the Naccio library.  It will
display a dialog box containing the text of the violation
and information on the safety property that is about to be
violated.  The user is presented with the option to
terminate the execution, or to ignore the violation and
allow execution to continue.

As it is defined, the NoDeleting property is probably not
satisfactory.  It prevents explicit deletion of existing
files, but does not prevent deleting a file by overwriting
its contents or renaming another file to its name.  A more
comprehensive property that prevents any modification of
existing files could be defined as:
    
    property NoBashingFiles {
     check  RFileSystem.openOverwrite (file: RFile),
         RFileSystem.openAppend (file: RFile),
         RFileSystem.preDelete (file: RFile),
          RFileSystem.renameNew (file: RFile, newfile:
     RFile),
         RFileSystem.renameReplace (file: RFile,
    newfile: RFile) {
       violation (``Destructive file manipulation
     prohibited.'');
      }
    }

A simpler definition would use the modifyExistingFile group
that groups all resource operations that alter the contents
of existing files:
    
    property NoBashingFiles {
     check RFileSystem.modifyExistingFile (file:
    RFile) {
      violation (``Destructive file manipulation
    prohibited.'')
     }
    }

Using resource groups makes the property more concise and
easier to understand.  It also means the property will not
need to be changed if new resource operations are added as
long as the modifyExistingFile group is appropriately
amended.

3.2.1     Adding State

One problem with these properties is that the violation text
provides no useful information about what file is being
manipulated.  The user cannot tell the difference between an
execution that is about to alter a junk file and one that is
about to alter an important file.  As is, it is impossible
to do this by modifying only the check action since the
RFile object passed to the resource operations does not
contain any information about the file it corresponds to.

In order to track this information, state must be added to
the RFile resource.  Naccio supports this using a state
block:
    
    stateblock FileNames augments RFile {
     addfield name: String;
    
      precode RFile (pathname: String) {
        name = pathname;
     }
    
     helper getName () returns String {
      return name;
     }
    }

This augments the RFile object with name, a String field
representing the name of the file.  The precode block
associated with the RFile constructor sets name to the value
of its parameter, a String that canonically identifies a
particular file.  This constructor is called to create an
RFile object before any operation that requires it is
called.  Since all RFile objects are created using this
constructor, the name is available wherever an RFile object
is used.  Safety properties can refer to the name of an
RFile object rfile, using rfile.name or by calling the
helper method getName.  It is useful to keep the state
maintenance and safety property checking code separate,
since many safety properties use the same state.

Figure 5 shows the NoBashingFiles property modified to use
the file name information to produce a more helpful
violation message.  The requires clause identifies the state
block that defined RFile.getName.  The state block is
defined in a separate file that is found using a naming
convention.  Properties can include multiple state blocks as
long as multiple state blocks do not use the same field or
helper routine name.
    
    property NoBashingFiles {
     requires FileNames;
     check RFileSystem.modifyExistingFile (file:
    RFile) {
      violation (``Destructive manipulation of file:'' +
    file.getName ());
     }
    }
             Figure 5.  NoBashingFiles property.

3.2.2     Use Limits
State can be also be used to make policies more precise.
For example, a property based on NoBashingFiles could do a
test on the file name to allow modification of files in the
/tmp/ directory but prohibit all other modifications of
existing files.  State can also be used to define policies
that place limits on the amount of a resource that may be
used over the course of an execution.  For example, the
LimitBytesWritten property shown in Figure 6 places a limit
on the total number of bytes that may be written to the file
system.

To enforce a limit on the number of bytes that may be
written, the property must keep track of the total number of
bytes written.  The TrackBytesWritten state block does this
by adding a field to the RFileSystem resource and defining a
postcode action for the write operation.  The body of the
postcode action will happen after all checking code
associated with the resource operation.  Hence, when
bytes_written is used in the check action of
LimitBytesWritten, its value is the total number of bytes
written already not including the upcoming call.  After all
the checking code has executed, the value is updated to
account for the upcoming write.


    stateblock TrackBytesWritten augments RFileSystem {
     addfield bytes_written: int = 0;
     postcode postWrite (file: RFile, n: int) {
      bytes_written += n;
     }
    }
    
    property LimitBytesWritten (limit: int) {
     requires TrackBytesWritten, FileNames;
     check RFileSystem.preWrite (file: RFile, n: int)
    {
      if (bytes_written + n > limit)
        violation ("Attempt to write more than " +
    limit + " bytes. Already written " +
         bytes_written + " bytes, writing up to " + n
    + " more to " + file.getName () + ".");
     }
    }
        Figure 6.  LimitBytesWritten Safety Property.

3.2.3     Composing Properties

This simplest way to combine properties is to intersect them
using the & operator.  The intersection of two safety
policies allows an execution only if both policies allow the
execution.  That is to say, the intersection of one or more
safety properties issues a violation whenever any of the
individual properties would issue a violation.  If more than
one of the properties would issue a violation for the same
resource operation, the violation reported by the first
property appears first.  Intersecting safety properties is
equivalent to merging all the check clauses into one
property in the same order they were intersected.

Another way to combine two safety properties is to weaken a
property with permissions that override violations.  All the
previous properties have been expressed negatively, in terms
of issuing violations before a prohibited manipulation is
about to happen and implicitly allowing everything else.  An
alternative way of defining properties is to assume nothing
is allowed unless it is explicitly permitted.  This has the
advantage that is it less likely for a policy author to
accidentally allow something dangerous.  Conversely, it is
more likely that a policy author will forget to allow
something that is needed by a harmless program.  To avoid
arguments about which approach is preferable, Naccio
supports both and provides rich enough property combination
mechanisms to allow both positive and negative properties to
be used.

A permission uses allow to indicate that the given resource
manipulation is permitted.  For example,
    
    permission AllowModifyDir (path: String) {
     requires FileNames;
     check RFileSystem.modifyExistingFile (file:
    RFile) {
      if (NCheck.inDirectory (file.getName (), path))
    allow ();
     }
    }

allows files in the directory identified by path to be
modified.  The inDirectory library function does a
comparison to determine if the file is contained within the
directory identified by path.  A property cannot use both
allow and violation.

By default, Naccio policies assume everything is allowed.
Hence, a permission only makes sense when it is combined
with a negative property.  The universal negative policy
would associate a check clause with every resource operation
that simply issues a violation (this is what the DisallowAll
policy used in Section 8.4 does).  Defining policies in
terms of permissions that override the universal negative
policy would satisfy the principle of fail safety that
recommends disallowing all security-relevant behavior that
is not explicitly allowed [Saltzer75].  This approach makes
sense when there is a small, fixed set of security-relevant
behavior, but becomes cumbersome when the class of behavior
considered to be security-relevant is large and flexible.
It would be undesirable if all policies had to be rewritten
when a new resource is added.  Since this is expected to be
fairly common with Naccio, Naccio's default is to allow
everything that is not explicitly prohibited.

Hence, permissions are only useful in a context where some
manipulations are already prohibited.  When a property is
weakened by a permission, violations in the property are
overridden by allowances in the permission.  For example,
    property NoBashingExceptTmp {
     (NoBashingFiles weaken AllowModifyDir (``/tmp/''))
    weaken AllowModifyDir (``/u/evs/tmp/'')
    }
defines a property that issues a violation whenever a file
not in the /tmp/ or /u/evs/tmp/ directories is modified.
The allowances in a positive property override violations in
a negative property.  If the weakening property calls allow
on a particular invocation of a resource operation, no
violations will be issued from that resource operation.
Another way to express the same property would be to compose
the positive properties first:
    property NoBashingExceptTmp {
     NoBashingFiles weaken (AllowModifyDir (``/tmp/'') &
    AllowModifyDir (``/u/evs/tmp/''))
    }
Weakening is useful for combining new policies with standard
policies that describe commonly allowed behavior.  For
example, JDKFilePermissions is a standard set of permissions
that allow files loaded by the JDK initializations and AWT
to be read.  A new safety policy that prevents reading files
except those loaded by the JDK initializations can be
expressed easily by writing a no reading property that
disallows all file reading and weakening it with
JDKFilePermissions.

In order to enforce a policy on an execution, all parameters
must be bound to real values.  This is done by instantiating
all parameterized properties with parameters.  We call a
property in which all parameters are bound a resource use
policy.  All parameters must be manifest constants.  Figure
7 shows the LimitWrite resource use policy that disallows
modification of any existing file or writing more than one
million bytes to the file system.  Properties that have no
parameters can also be used directly as resource use
policies.
    policy LimitWrite {
     NoBashingFiles & LimitBytesWritten (1000000)
    }
         Figure 7.  LimitWrite resource use policy.

3.3  Standard Resource Library

The standard resource library is a set of resource
descriptions that correspond to the security-relevant
resource manipulations that are common to most modern
platforms.  The standard resource library does not attempt
to exhaustively cover all possible ways of manipulating
resources, but instead is designed to include the
manipulations commonly used in security policies that are
universal enough to apply to most platforms.  Since all
Naccio implementations provide the same standard resource
library, policies written in terms of these resources are
portable across different platforms.

The standard resource library includes the RFile and
RFileSystem resources introduced in Section 3.1, as well as
resources corresponding to the network, the display, system
threads, audio devices, and the system environment.  It
contains a total of 122 resource operations in thirteen
resource descriptions.  Additional resources may be needed
as new devices are attached to the system.  For example, if
a camera is used a corresponding resource should provide
operations that correspond to taking and transmitting
pictures.  There may also be resources that are unique to a
particular platform.  For example, Naccio/Win32 includes a
resource representing the Windows registry.

     Network
In most modern operating systems, the network can be used in
three distinct ways:  a persistent connection can be created
to a remote host, and data sent and received through it; a
server socket can be created to listen for incoming
connections; and individual datagram packets may be sent or
received without a persistent connection.  Since policies
should be able to distinguish between each type of network
use, we provide different resource objects for identifying
them.  Conversely, the network operations should make it
easy to write network use policies that place restrictions
on the remote hosts that may be contacted and limits on the
number of bytes transmitted.  To support easy definition of
both kinds of policies, the network resources provide
operations corresponding to the different types of network
connections, but also groups operations so policies that do
not depend on the type of network connection can be defined
concisely.

The network resources are shown in Figure 8.  Unlike the
file system resources, the RNetConnection resource maintains
some state and provides an observer.  An observer is a
routine that reveals some information about a resource but
does not modify anything.  The RNetConnection stores the
local and remote addresses of the connection in state
variables when an RNetConnection is constructed.  The
observers make these values available through a function
call.

The observers can be used in resource group member lists to
map members to the group operation.  This is done in the
definition of the connectRemoteAddress resource group that
takes an RNetAddress parameter representing the remote
address.  To make the preOpenConnection resource operation
match the parameter types of the connectionRemoteAddress
group, we need to convert its RNetConnection parameter into
the appropriate RNetAddress object corresponding to the
remote address.  We do this by calling the getRemoteAddress
observer defined by the RNetConnection resource.

     Display
The display is represented by the RDisplay global resource,
and RWindow resource objects identify individual windows.
The main security threats involving the display are denial
of service annoyance attacks that take over the screen with
superfluous windows.  A more serious threat is attacks that
create rogue windows that appear to be part of a legitimate
application and trick the user into providing trusted
information (such as a password) to a malicious program.
This threat can be mitigated by a policy that requires that
all windows created from untrusted programs have a
distinctive appearance that distinguishes them from
trustworthy windows.

The RDisplay resource includes operations for creating new
windows and for setting properties of windows or
manipulating existing windows.  It also contains operations
that correspond to enabling a window to receive events from
the mouse or keyboard and receiving those events.  These
could instead be treated as separate resources, but since
events are usually directed at a window it is convenient to
include them with the display.  By using a state block to
track user input events, policies can determine if a
resource manipulation is permitted based on the history of
user activity.  Since windowing systems are likely to vary
more across platforms than other
global resource RNetwork
 operations
  initialize ()                    Called at the beginning
 of an execution. terminate ()
 Called immediately before execution terminates.

   preOpenConnection (connection: RNetConnection)
Called before opening connection.
   postOpenConnection (connection: RNetConnection)
Called after opening connection.
   closeConnection (connection: RNetConnection)
Called after closing connection.
   preOpenListener (listener: RNetListener)  Called before
opening listener for server connections.
   postOpenListener (listener: RNetListener) Called after
opening listener for server connections.
   preAccept (listener: RNetListener)             Called
before accepting a connection using listener.
   postAccept (listener: RNetListener, connection:
RNetConnection)
                           Called after accepting connection
using listener.
   closeListener (listener: RNetListener)
Called after closing listener.
   openDatagramPort (port: RNetListener)     Called before
opening port for datagrams.
   closeDatagramPort (port: RNetListener)         Called
before closing port.

   preSendDatagram (local: RNetAddress, remote: RNetAddress,
nbytes: int)
    Called before up to nbytes are sent from local to remote
using a datagram.
     preSendConnection (connection: RNetConnection, nbytes:
int)
         Called before up to nbytes are sent through
connection.
   preReceiveDatagram (local: RNetAddress, nbytes: int)
    Called before a datagram may be received at local.
   postReceiveDatagram (local: RNetAddress, remote:
RNetAddress, nbytes: int)
    Called after nbytes are received from remote to local.
   ... // other operations for postSend, preReceive and
postReceive for datagrams and connections elided
   group connectRemoteAddress (address: RNetAddress)
Called before any contact with address.
    preOpenConnection (connection: RNetConnection)
      : connectRemoteAddress (connection.getRemoteAddress
()),
    postAccept (listener: RNetListener, connection:
RNetConnection)
      : connectRemoteAddress (connection.getRemoteAddress
()),
    preSendDatagram (local: RNetAddress, remote:
RNetAddress, nbytes: int)
      : connectRemoteAddress (remote),
    postReceiveDatagram (local: RNetAddress, remote:
RNetAddress, nbytes: int)
      : connectRemoteAddress (remote);  // can't know remote
before receive, must check after

   group preSend (remote: RNetAddress, nbytes: int)
          preSendDatagram (local: RNetAddress, remote:
RNetAddress, nbytes: int)
      : preSend (remote, nbytes),
    preSendConnection (connection: RNetConnection, nbytes:
int)
      : preSend (connection.getRemoteAddress (), nbytes);

   ... // similar groups for postSend, preReceive and
postReceive elided
   ... // operations related to multicasting and revealing
hostnames elided.

resource RNetConnection
 state local, remote: RNetAddress; // Identfy the local and
remote addresses for this connection.
 operations
     RNetConnection (l: RNetAddress, r: RNetAddress)
    Constructs an RNetConnection object for communication
    between l and r.
    { local = l; remote = r; }
 observers
   getLocalAddress () returns RNetAddress         { return
local; }
   getRemoteAddress () returns RNetAddress   { return
remote; }
 
// RNetAddress and RNetListener not shown.
                Figure 8.  Network Resources.

resources, it is likely that Naccio implementations will add
additional operations to the RDisplay resource to include
platform-specific operations that provide more precise ways
of constraining display use.

     Threads
The RSystemThreads global resource provides operations
corresponding to manipulating threads.  It includes
operations for creating new threads or thread groups,
starting and destroying threads, suspending and resuming
threads, changing the priority of a thread, and revealing
information about a thread or thread group.  The RThread and
RThreadGroup resources are used to identify threads and
groups of related threads.Audio
The speaker can be used in an annoyance attack.  To support
policies that constrain its use, the RAudio global resource
contains operations corresponding to ringing the system bell
and playing audio files.

System Environment
The RSystem resource is used to collect operations that do
not correspond well to a conceptual resource.  It includes
operations for observing and setting environment variables,
and is often extended with platform-specific system
operations.

The RSystem resource also includes special initialize and
terminate operations that are called at the beginning of an
execution.  The RSystem initializer is called before any
other global resource initializers.  The RSystem terminator
is called after every other global resource terminator.  The
RSystem initializer is also unique in that it has an
argument that passes in the command-line arguments.  A
policy can use a state block that attaches checking code to
RSystem.initialize to record these values, and then use the
value of the command-line arguments to determine if a
resource manipulation is permitted.

3.4  Policy Expressiveness

In standard safety policies, the effects of checking code
are limited to raising violations, modifying internal state,
and doing computations that are invisible to the user.  The
policy has no noticeable effect on an execution (other than
a performance penalty) unless a violation is detected.  We
can view a Naccio standard safety policy as a predicate on
an execution --- it is true if no violation is issued, and
false if a violation is issued.

Schneider defines Class EM, a class of enforcement
mechanisms that work by monitoring a target system and
terminating any execution that is about to violate the
policy [Schneider98].  Security kernels, reference monitors,
and nearly all run-time based enforcement mechanisms are in
Class EM.  The set of policies that can be enforced by
mechanisms in Class EM is defined as those policies that can
be expressed as predicates on execution prefixes.

A security policy is defined as a predicate on a set of
executions.  A program satisfies a security policy if the
predicate is satisfied by the set of all possible executions
it can produce.  Policies like information flow require
knowledge of more than one execution, since it is not clear
whether a particular execution of a program reveals
information without knowing what other executions do.
Hence, these policies cannot be enforced by mechanisms in
Class EM.  Enforcing these policies requires static analysis
of the program text.

Those security policies that can be defined as a predicate
on a single execution are known as security properties.  Not
all security properties, however, are in Class EM, since
they may depend on knowing the future.  For example,
liveness properties depend on knowing something must happen
at some future point in an execution.  Class EM mechanisms
cannot enforce liveness properties since they can only probe
what has already happened.

The subset of security properties that can be defined by
looking only at the past and present are defined to be
safety properties.  A safety property is a predicate on an
execution prefix.  If it is false at some point in an
execution, it is false for all following execution points.

The policies that can be enforced using an enforcement
mechanism in Class EM are a subset of safety properties.
The subset is defined by how much information the
enforcement mechanism can probe.  An enforcement mechanism
that can probe all system information after every
instruction could enforce all safety properties.

To satisfy the requirements of class EM, the probe should
have no effect on the system and should be completely
unnoticeable by the executing program.  This is not possible
if the probe is implemented in software running on the same
machine as the program it is probing.  At a minimum, it uses
CPU cycles that would otherwise be available to the
execution.  In some cases, it may need to manipulate
resource also.  For example, to enforce the NoBashingFiles
property introduced in Section 3.2 using Naccio/JavaVM, it
may be necessary to examine the file system to determine if
a file already exists (Section 4.2.2 shows how the platform
interface is written to do this).  We consider resource
manipulations done by the checking code to be separate from
the behavior of the program.  These manipulations are done
without any checking enforced.  This means policy authors
must be wary that an attacker cannot exploit code introduced
to do checking.

Aside from the side effects introduced by probing, Naccio
standard safety policies are in Class EM.  They observe the
behavior of an execution through resource operations and
issue a violation to terminate execution when a policy
violation is about to occur.  The subset of safety
properties that can be defined as Naccio standard safety
properties is defined by the resource operations defined by
the standard resource library.  Naccio can detect violations
and observe and modify state only at execution points
corresponding to resource operations, and can only observe
system information available through parameters to resource
operations (as well as some global system information that
can be observed through calls to Naccio library functions).

Certain safety properties cannot be defined using the
standard resources.  For example, since RFileSystem.preWrite
takes an integer parameter revealing the number of bytes to
be written but does not have a parameter corresponding to
the actual data written, we cannot write a policy that
constrains the actual values of bytes that may be written.
In the next chapter, we describe how resource operations are
given meaning using a platform interface and how new
resource operations and safety policies can be defined by
altering the platform interface.  In addition, by removing
some of the restrictions placed on standard safety policies,
Naccio can be used to define and enforce policies that alter
program behavior.  Because these policies do not simply
probe system information and decide to terminate an
execution, they do not fit Schneider's definition of a
security policy.  As a result, Naccio is not strictly in
Class EM.








Chapter 4
Describing Platforms



The previous chapter showed how a safety policy is defined
in terms of resource descriptions.  To have meaning, there
must be a way of viewing the way a particular platform
manipulates actual resources in terms of those abstract
resource descriptions.  This is done using a platform
interface, an operational specification of a platform in
terms of its resource manipulations.  Naccio implementations
include a platform interface that describes the platform in
terms of the standard resource library.  Changing the
platform interface allows new resource operations to be
defined and more safety policies to be described and
enforced.  We call policies that are defined by altering the
platform interface extended safety policies.

4.1  Platform Interfaces

In order to enforce a policy defined in terms of abstract
resources, we need a way to model an execution in terms of
those resources.  The platform interface provides an
operational specification of a concrete execution platform
in terms of a set of resource descriptions.  A different
platform interface is needed for each execution platform and
each set of resource descriptions.  The platform interface
provides a way to map events during a program execution to
abstract resource manipulations.  Since the specification is
operational, it is easy for the policy compiler to convert
it to code that calls the resource operations in the
appropriate way.

We can view the platform interface as a probe that can see
certain system events.  Based on those events, it can
execute bookkeeping code and call abstract resource
operations that perform the checking necessary to enforce a
policy.  A Naccio implementation determines what events are
visible to the probe, and where in the execution chain it
sees them.  The events visible determine what resource
operations can be defined and this limits the class of
policies that can be expressed and enforced.  For example,
if the platform interface can only see manipulations of the
file system then resource operations relating to
manipulating the network cannot be defined.  If the platform
interface can see the entire state of the machine before and
after every instruction, then all policies in class EM can
be enforced.  Policies defined using a platform interface
that can see all system events, however, are likely to be
cumbersome and expensive to enforce.  Instead, the platform
interface is defined at a level that allows only certain
events to be seen.  For example, a platform interface might
be defined in terms of calls in the system API.  This would
make the platform interface easier to create and understand,
and would simplify the work of the policy compiler and
program transformer.  It would not support the definition or
enforcement of policies that constrain resources that can be
manipulated without going through system API calls, such as
referencing a memory location.

A Naccio implementation must also determine where in the
execution chain the platform interface probe is done.  This
level determines the trust boundary between what is
described by the platform interface and what is considered
part of the program.  Operations below the level described
by the platform interface execute without safety checking
and are assumed to manipulate resources in the way specified
by the platform interface.  Operations above the level
described by the platform interface are transformed to
perform the safety checking defined by the resource use
policy.  The lowest conceivable place for the platform
interface is at the level of physical hardware devices.  For
example, a disk drive controller could be designed to call a
resource operation before writing a bit to the disk or a
firewall could monitor network traffic and call appropriate
resource operations.  This would require hardware support
not readily available today.  If it were available, however,
this would allow safety policies to be enforced without
trusting anything other than the hardware controllers.  The
difficulty would be mapping these events to resource
operations.  The disk controller can provide information
about which segment on the disk is being written, but
probably cannot convert that to a meaningful pathname.  This
requires operating system support, and would expand the
trusted computing base to include the relevant system code.
Another difficulty with a hardware-level platform interface
is the problem of associating a particular manipulation with
the program that caused it.  Again, the hardware traps will
need to rely on operating system level code to map requested
actions to the program instigating them and the appropriate
safety policy.  This would require substantial run-time
overhead.  Since the effective policy is not known until the
application is determined, the overhead is required even for
simple policies or unconstrained executions.  For most
situations, hardware-level safety checking is not practical
or appropriate.  There are situations, however, where safety
is crucial enough that it is desirable to place the safety
checking at as low a level as possible so that bugs in the
system library do not lead to policy violations.  For
example, it would be appropriate for medical devices (such
as the Therac-25 mentioned in Section 1.1) with custom
hardware and control software.

The next level to consider for the platform interface is at
the level of machine instructions.  A platform interface at
this level would allow any instruction to be mapped to
resource operations.  Trust would be confined to the
behavior of individual machine instructions, although as
with the hardware-level checking, it is likely that some
information provided by the operating system would be
necessary in mapping instructions to meaningful objects.
The main problem with defining a platform interface at the
level of machine instructions is that it would be hard to
produce and understand.  Recognizing all sequences of
instructions that represent a function call, and defining a
platform interface in terms of those instruction sequences
is likely to be a cumbersome and error-prone task.

Above the individual machine instructions, we can consider a
platform interface at the level of the system API.  Typical
modern operating systems have a protected kernel, and allow
programs to manipulate most resources only through calls to
routines provided by that kernel.  The system API provides a
convenient place for the platform interface since it is
usually well documented and structured to provide an
abstract way to manipulate resources.  Placing the platform
interface at this level has other advantages in implementing
the policy enforcement mechanisms.  Unlike lower-level
platform interfaces that would require correspondingly low-
level transformations of both the program code and system
API code to enforce a policy, a policy defined at the level
of the system API can be enforced by interposing checking
code at system call boundaries.  This requires that the
execution platform provides a clear distinction between the
system API and application code, and that this interface be
maintained securely.  One disadvantage of placing the
platform interface at this level are that certain resource
manipulations, such as allocating or referencing memory, may
not be visible through calls to the system API.  Another
problem is that we must trust to system API implementation
to manipulate resources in the way described by the platform
interface.  This makes the system API part of the trusted
computing base and means attackers can exploit bugs in the
system API to circumvent the safety policy.  The other issue
with a platform interface at the level of the system API is
that it is necessary to ensure that programs cannot
manipulate constrained resources without using the standard
system API.  Despite these disadvantages, the system API
seems to be the best place for the platform interface for
most Naccio implementations.  Both of our prototype
implementations use platform interfaces at the level of a
system API.  Naccio/JavaVM uses a platform interface that
describes the Java API (classes in the java. packages) and
Naccio/Win32 uses a platform interface at the level of the
Win32 API.  Sections 4.2 and 4.3 describe these platform
interfaces.

We can also consider platform interfaces at a higher level.
A platform interface could describe a commonly used library
such as Microsoft Foundation Classes (MFC) that is
implemented using the Win32 API.  This would support more
higher-level distinctions (and hence, more precise policies)
than could be written with a platform interface at a lower
level.  For example, we could use a platform interface at
the level of MFC to define different resource operations
corresponding to opening a file selected by the user using a
standard dialog box and opening a file without user
prompting.  Providing a similar distinction at a lower level
would be possible, but very awkward.  It would be necessary
to examine the properties of the window to see if it looks
like a standard file request dialog and the input from the
user to determine what file was selected.  Another option
would be to write a platform interface that describes
application level events.  This would allow policies to be
defined in terms of objects that are meaningful at the
application level but not at the system such as application
data structures.  The problem with higher-level platform
interfaces is that they only work for a subset of programs
that use those higher-level libraries.  Programs that
manipulate constrained resources in other ways must be
disallowed.  This could be done by a static analysis that
the code never uses system API calls directly.  It would
summarily reject many harmless programs, however, simply
because they were not written using the higher-level
library.

4.2  Java API Platform Interface

Naccio/JavaVM enforces safety policies on executions of Java
programs that are collections of JavaVM classes.  To enforce
Naccio policies on Java classes, we need a platform
interface that maps a Java execution to a sequence of
abstract resource operations.

4.2.1     Platform Interface Level

Naccio/JavaVM uses a platform interface at the level of the
Java API.  Another reasonable option would be to put the
platform interface at the level of individual byte code
instructions.  This would allow for resources to be
described that correspond to manipulations done below the
level of the Java API, such as memory references.  All high-
level system resources including the file system, network,
and display are accessible to Java programs only through
native methods.  If an untrusted program is not permitted to
install its own native methods or call native methods
installed by other programs, the only way it can manipulate
these resources is through calls to the Java API.  Placing
the platform interface at the level of the Java API allows
nearly all security-relevant manipulations to be constrained
and allows the platform interface to be described at a well-
documented and well-defined level.  Further, a platform
interface at the level of the Java API provides a convenient
place to introduce wrappers.

To define the platform interface, we could examine the API
specification and write a wrapper for each API routine that
describes its resource usage.  This would involve
substantial work, and depend on the API specification being
correct and describing resource usage of all routines in
sufficient detail.  We can simplify the task of writing a
Java API platform interface, however, by noting that all
relevant resource manipulations must eventually be done by
native methods.  This means a platform interface for the
Java API could describe the resource manipulations done by
native methods explicitly, and determine the resource
manipulations done by other routines based on their code
(either statically or at run-time).  This would limit the
amount of work necessary to write the platform interface to
describing the native methods in a particular Java API
library implementation.7

A problem with this approach is that it ties the platform
interface closely to a particular API implementation,
instead of to the specification of the Java API.  Since we
must describe private native methods, the same platform
interface could not be reused with a different
implementation of the Java API.  The other problem with
specifying the platform interface at the level of native
methods is that it may be difficult to determine enough
information about the context of a call to pass appropriate
information to the resource operations.

Instead, the Naccio/JavaVM platform interface describes only
the specified parts of the Java API. It does not describe
any private API methods since the Java API does not specify
these.  It does, however, support a pass-through semantics
so that not every API routine needs to be described
explicitly.  For routines that are not explicitly described,
the routines they call are checked as if they were called
directly by the untrusted program.  We can use implicit
specifications only for routines that do not directly or
indirectly call any native methods whose behavior is not
explicitly described.  Hence, the platform interface must
explicitly describe any API routine that has a native
implementation, that calls a private nati