An important goal of the
.NET Framework is to facilitate the development of highly
distributed, component-based systems. With the .NET Framework, you
can easily create applications that utilize code from different
publishers, dynamically loading assemblies from different locations
as required. Internet Explorer is an early example of this flexible
architecture: downloading and executing .NET controls on demand, and
giving the user a rich interface to the Web. Over the coming years,
you can expect to see many more .NET applications componentized and
delivered "on demand."
Traditionally, code executes using the identity, roles, and
permissions of the user that runs it. As we discussed in the previous
section, role-based security is important for ensuring system
security. However, in a connected world where distributed systems and
highly mobile code are the order of the day, it is insufficient to
base system security decisions solely on the permission granted to
the user running an application. Faulty code can damage files to
which the user has access, or, worse, malicious code can take
advantage of the user's authority to perform all
kinds of mischief. A highly trusted user (with extensive system
permissions) cannot freely download and run an application from an
untrusted source for fear of what the code may do to his system and
other systems on his network.
Many companies tackle the problem of code security by
"locking down" their
users' desktops, restricting the applications that
people can install and run. This heavy-handed approach requires
extensive engineering and support resources to maintain and reduces
the utility and flexibility of each user's computer.
The .NET Framework includes an important new feature known as
code-access security (CAS), which provides
fine-grained control over the operations and resources to which
managed code has access. With CAS, access is based on the identity of
the code, not the user running it. Some examples of operations
protected by CAS include:
Creating, deleting, and modifying files and directories
Reading from and writing to the Windows registry
Using sockets to accept or initiate a network connection
Creating new application domains
Running native (unmanaged) code
Other pre-.NET technologies have offered code-level security by
differentiating between the identity of code from the user running
it. These technologies include Java with its
"sandbox" approach and Windows
Internet Zones, which restrict the functionality of downloaded
controls. However, few of these technologies provide the level of
control, flexibility, and extensibility of CAS.
1. Evidence, Security Policy, and Permissions
The three key elements of code-access security are
evidence, security
policy, and permissions.
When the runtime loads an assembly, it inspects various
characteristics of the assembly (evidence) to determine an identity
for the code. Based on a configurable set of rules (security policy),
the runtime uses the assembly's evidence as input to
a process named policy resolution. The
result of policy resolution is a set of
protected operations and resources to which the code within the
assembly has access (permissions).
In the course of execution, code attempts to perform a variety of
operations. If the code attempts to perform a protected operation,
then the runtime will look at the permissions granted to the assembly
containing the code and determine if they include the permission
necessary to allow the action to go ahead. Figure 1 illustrates the relationship between evidence,
security policy, and permissions during policy resolution. Here is a
detailed account of each element:
Evidence
-
Evidence includes the
characteristics
of an assembly used by the runtime to establish an identity for the
assembly. Establishing an identity for each assembly allows the
runtime to apply security policy in order to determine the set of
permissions to grant to the assembly. Evidence includes
characteristics, such as the strong name of an assembly, the web site
from where an assembly is loaded, or the publisher certificate used
to sign an assembly.
- Security policy
-
Security policy is a configurable set of rules that the
runtime uses during policy resolution to determine which permissions
to grant to an assembly by evaluating the assembly's
evidence. For example, security policy could dictate that all code
downloaded from the web site http://www.oreilly.com has permission to
write to the C:\Test.txt file, or that only code
signed with Microsoft's publisher certificate can
run unmanaged code.
- Permissions
-
Permissions represent the authority of an
assembly's code to access protected operations and
resources. Permissions can be very specific, such as permission to
open a network connection to the web site http://www.oreilly.com, or very broad, such
as permission to read and write anywhere on the hard disk.
2. Windows Security and Code-Access Security
CAS does not replace or circumvent the security provided by the
Windows operating system. As shown in Figure 2,
CAS is effectively another layer of security that managed code must
pass through before it can interact with protected system resources,
such as the hard disk and the Windows registry. The important
difference between CAS and Windows security is that CAS bases
security decisions on the identity of the code performing an action,
whereas Windows bases security decisions on the identity of the user
on whose behalf the code is running.
For example, if a managed application tries to write a file to the
hard disk, CAS first determines if the code has the necessary
authority to write to the file by evaluating the permissions granted
to the assembly when it was loaded. If the code does not have the
necessary authority, then the write operation fails and the runtime
throws a security exception. On the other hand, if the code does have
permission, the runtime interacts with the operating system on behalf
of the current user to access the file. If Windows file permissions
do not allow the user to write to the specified file, then access is
denied and the runtime throws an exception. If the user has write
permission, then the file is written.
As Figure 2 also shows, CAS controls the ability
of managed code to call unmanaged (native) code, such as the Win32
APIs. This is important, because when a managed application is
allowed to call into unmanaged code, there is no CAS layer to
restrict what the unmanaged code can do on behalf of the managed
code. Instead of having permissions based on code identity, you are
back to the situation where only the permissions of the current user
restrict the code's actions. As we discussed at the
beginning of this section, this is not sufficient when dealing with
mobile code from potentially unknown and untrusted sources.
Finally, CAS also controls access to functionality that is internal
to the CLR and does not affect operating system resources, such as
the ability to create an application domain.