Assemblies
contain code that is executed when you run the application. As for the
operating system and for any development environment, code is executed
according to security rules that prevent the code from unauthorized
access to system resources. The .NET Framework 4.0 introduces a new
security model, highly simplified if compared to the previous Code
Access Security platform. The code will still be classified as fully
trusted and partially trusted, in which full trust means that the code
has elevated permissions for accessing resources, whereas partial trust
means that the code is restricted by the permissions it has. The
security model provided by the CLR is now easier to understand and to
implement, differently from what Code Access Security was in the past.
Extensive changes have
been introduced to the security model for code access, whereas the
role-based security model basically offers the same features as in the
past. For this reason in this section we cover changes about the code
access security model, whereas for further details on the role-based
model, you can check out the MSDN documentation here: http://msdn.microsoft.com/en-us/library/shz8h065(VS.100).aspx.
|
The following are the major changes in the security model offered by .NET 4.0:
The transparency model
has been enforced and applied to the .NET Framework and managed
applications. Basically the transparency model can separate code that
runs as part of the application (transparent code) and code that runs
as part of the .NET infrastructure (critical code). As a result,
critical code can access privileged resources, such as native code,
whereas transparent code can only access resources allowed by the
specified permissions set and cannot invoke or inherit from critical
code; with the transparency model groups of code isolated based on
privileges. Such privileges are divided into full-trust and
partial-trust in the sandboxed model.
The enforcement of the
transparency model is also the reason why the .NET Framework
configuration tool is no longer available for setting CAS policies.
The sandboxed model
allows running code in a restricted environment that grants code the
only permissions it actually requires, and the natural place for the
model is the application domains described in the previous section.
Desktop
applications always run as fully trusted. This is also true for
applications started from Windows Explorer, a command prompt, and from
a network share.
Permissions are still a central part in security, but some security actions from the System.Security.Permission.SecurityAction class have been deprecated. In detail they are Deny, RequestMinimum, RequestOptional, and RequestRefuse.
You can expose partially trusted assemblies via the AllowPartiallyTrustedCallers attribute.
To enable constraints on types that can be used as evidence objects, .NET Framework 4 introduces the System.Security.Policy.EvidenceBase base class that must be inherited from all objects that want to be candidate as evidence.
The transparency model is not
new in the .NET Framework; it was first introduced with version 2.0 as
a mechanism for validating code efficiency. In .NET 4 it has been
revisited (this is the reason why it is also known as Level 2) and
provides an enforcement mechanism for code separation.
|
The next sections provide explanations and code examples about new security features in .NET Framework 4 with Visual Basic 2010.
Permissions
With the exceptions
described in the previous bulleted list for deprecated security
actions, applying permissions in the new security model is similar to
the previous versions of the .NET Framework. This means that you can
leverage permissions from the System.Security.Permissions namespace, such as FileIOPermission, UIPermission, IsolatedStoragePermission, and EnvironmentPermission.
The following code demonstrates how you use the declarative syntax for
implementing a class that requires the caller code having the FileIOPermission to execute. Such a class simply implements a method that returns an XDocument from a text file:
'The caller code will need the FileIOPermission permission
'with unrestricted access otherwise it will fail
<FileIOPermission(Security.Permissions.SecurityAction.Demand,
Unrestricted:=True)>
Class XmlParse
Shared Function String2Xml(ByVal fileName As String) As XDocument
'Expects an Xml-formatted string
Return XDocument.Parse(fileName)
End Function
End Class
You can also use the imperative syntax, which looks like this:
Dim fp As New FileIOPermission(PermissionState.Unrestricted)
Try
fp.Demand()
Catch ex As Security.SecurityException
End Try
You create an instance of the required permission and then invoke Demand for checking if the application has that level of permissions. If not, a System.Security.Security Exception is thrown.
The Transparency Level 2
By default, when you create a
new application it relies on security rules provided by the
Transparency Level 2 of .NET Framework 4.0. The level name has this
form to allow distinction from the old transparency level of previous
.NET versions (known as Transparency Level 1). So the Transparency
Level 2 security rules are applied implicitly, but a better idea is
applying them explicitly by applying the System.Security.SecurityRules attribute that can be added at the assembly level as follows:
<Assembly: SecurityRules(Security.SecurityRuleSet.Level2)>
Applying the attribute
explicitly is appropriate for code reading and future maintenance and
avoids confusions. This level of enforcement brings into the .NET
Framework some new ways of thinking about security policies. Most rely
on the concept of host,
where this means an environment is responsible for executing
applications; ClickOnce, ASP.NET, and Internet Explorer are host
examples. For code trust, applications that are not hosted, such as
programs launched from a command prompt, from Windows Explorer or from
a shared network path, now run as full-trust. Instead, hosted or
sandboxed applications still run according to host-based policies and
run as partial-trust. For hosted and sandboxed applications, it is
worth mentioning that they are considered as transparent
because they run with the limited permissions set granted by the
sandbox. This means that you will no longer need to check for
permissions when running partially trusted code, because transparent
applications run with the permissions set granted by the sandbox, so
your only preoccupation should be targeting the sandbox permissions set
and to not write code requiring the full-trust policy. Talking about
transparency, it is important to mention that its mechanism can
separate code that is part of the .NET infrastructure (and that thus
requires high privileges such as invoking native code), which is called
critical code, and code that is part of the application, also known as transparent code.
The idea behind the scenes is separating groups of code based on
privileges. When working with sandboxes, such privileges are of two types:
fully trusted, which is the unrestricted level, and the partially
trusted, which is the level restricted to the permission set
established in the sandbox.
With the Transparency Level 2 enabled, desktop applications run as full-trust.
|
The System.Security.SecurityRules
attribute is not the only one that you can apply for establishing
permissions rules. There are other attributes available, summarized in Table 1.
Table 1. Security Attributes
Attribute | Description |
---|
SecurityTransparent | Specifies
that the code is transparent, meaning that it can be accessed by
partially trusted code, that it cannot allow access to protected
resources, and that it cannot cause an elevation of privileges. All
types and members are transparent. |
SecurityCritical | Code
introduced by types exposed from the assembly is considered as
security-critical meaning that it can perform operations that require
an elevation of privileges, whereas all other code is transparent.
Methods overridden from abstract classes or implemented via an
interface must be also explicitly marked with the attribute. |
SecuritySafeCritical | Specifies that types expose critical code but allows access from partially trusted assemblies. |
If you do not specify any other attribute other than SecurityRules,
for fully trusted assemblies the runtime considers all code as
security-critical, thus callable only from fully trusted code, except
where this could cause inheritance violations. If the assembly is
instead partially trusted, specifying no attribute other than SecurityRules
will make the runtime consider types and members as transparent by
default but they can be security-critical or security-safe-critical.
For further detail on inheritance in the transparency model and on
attributes listed in Table 46.1, visit the following page in the MSDN Library: http://msdn.microsoft.com/en-us/library/dd233102(VS.100).aspx.
So this is the reason why it is opportune to explicitly provide the
most appropriate attribute. The following is an example of applying
both the SecurityRules and SecurityTransparent attributes:
<Assembly: SecurityRules(Security.SecurityRuleSet.Level2)>
<Assembly: SecurityTransparent()>
Class Foo
End Class
Transparency
enforcements are handled by the Just-in-Time compiler and not by the
CLR infrastructure. This means that if you apply the SecurityTransparent
attribute to an assembly, the assembly cannot call transparent and
security-safe-critical types and members independently from the
permissions set (including full-trust). In such a scenario, if the code
attempts to access a security-critical type or member, a MethodAccessException will be thrown.
|
Sandboxing
You can execute partially
trusted code within a sandbox that runs with the specified permissions
set. Code, including assemblies, executed within the sandbox will be
also granted to just the specified permissions set. To create and run a
sandbox, you need an instance of the AppDomain class. The example here creates a sandbox for running an external assembly given the LocalIntranet zone’s permissions. Before showing the sandbox example, follow these steps:
1. | Create a new Console application and name the new project as ExternalApp.
|
2. | In the Main method simply add a Console.Writeline statement for showing whatever text message you like.
|
3. | Build the project; then create a new folder named C:\MyApps and copy the newly generated ExternalApp.exe into C:\MyApps.
|
Such steps are
required to have a simple external assembly to run inside the security
sandbox. Now close the ExternalApp project and create a new Console
project, naming it SandBox. The goal is to create a sandbox with LocalIntranet
permission and run an external assembly inside the sandbox so that this
external application will also be granted the same permissions. When
ready, first add the following Imports directives:
Imports System.Security
Imports System.Security.Policy
Imports System.Reflection
Now move inside the Main
method. The first thing you need is an Evidence object that you assign
with the required permissions set, as demonstrated by the following
code:
Dim ev As New Evidence()
ev.AddHostEvidence(New Zone(SecurityZone.Intranet))
When you have the Evidence instance, you can get a sandbox with the specified permissions as demonstrated by the following line:
Dim permSet As PermissionSet = SecurityManager.GetStandardSandbox(ev)
The SecurityManager.GetStandardSandbox
returns a sandbox limited to the specified permissions. This sandbox
will be used later when running the external assembly. As an alternative you can set your own permissions creating your custom permissions set using the PermissionSet object as follows:
Dim permSet As New PermissionSet(Permissions.PermissionState.None)
permSet.AddPermission( _
New SecurityPermission(SecurityPermissionFlag.Execution))
permSet.AddPermission(New UIPermission(PermissionState.Unrestricted))
At this point we can put our hands on application domains. The first thing to do is create an instance of the AppDomainSetup class for specifying the working directory of the external assembly:
Dim ads As New AppDomainSetup()
ads.ApplicationBase = "C:\MyApps"
At this point we just need to set the host Evidence and then create the AppDomain, passing the security information, finally invoking AppDomain.ExecuteAssembly to run the sand-boxed assembly:
Dim hostEvidence As New Evidence()
Dim sandbox As AppDomain = AppDomain.
CreateDomain("Sandboxed Domain", hostEvidence, ads, permSet, Nothing)
sandbox.ExecuteAssemblyByName("ExternalApp")
The AppDomain.CreateDomain
method has an overload that allows creating an application domain with
a permissions set. Because the application domain has security
permissions, an instance of the Evidence class is required to tell the runtime that the assembly will be affected by such permissions. Other arguments are the AppDomainSetup
instance and the permissions set under which the external assembly is
going to be run. The last null argument can be replaced with a
reference to the strong name, in case you want to add it to the full
trust list. This would first require the current application to be
signed with a strong name and then by getting a reference to the strong name via the System.Security.Policy.StrongName class as shown in the following line:
Dim fullTrustAssembly As StrongName = Assembly.
GetExecutingAssembly.Evidence.GetHostEvidence(Of StrongName)()
The Assembly.Evidence.GetHostEvidence(Of StrongName) method returns the reference to the strong name. (The System.Assembly class is discussed in the next chapter on Reflection). Finally, you pass the strong name reference to AppDomain.CreateDomain as follows:
Dim sandbox As AppDomain = AppDomain.
CreateDomain("Sandboxed Domain", hostEvidence, ads,
permSet, fullTrustAssembly)
Listing 1 shows the complete code example for your convenience.
Listing 1. Running a Sandboxed Assembly
Imports System.Security Imports System.Security.Policy Imports System.Reflection
Module Module1
Sub Main() Dim ev As New Evidence() ev.AddHostEvidence(New Zone(SecurityZone.Intranet))
Dim permSet As PermissionSet = SecurityManager.GetStandardSandbox(ev)
Dim ads As New AppDomainSetup() ads.ApplicationBase = "C:\MyApps"
Dim hostEvidence As New Evidence() Dim sandbox As AppDomain = AppDomain. CreateDomain("Sandboxed Domain", hostEvidence, ads, permSet, Nothing)
'The assembly runs in a LocalIntranet sandboxed environment sandbox.ExecuteAssemblyByName("ExternalApp") End Sub End Module
|
Working
with sandboxes can include complex scenarios. Particularly, you might
have the need to execute not-trusted code from an external assembly
with customized permissions sets. This also requires advanced
application domains concepts. Fortunately the MSDN Library provides an
interesting walk-through covering these scenarios, available at http://msdn.microsoft.com/en-us/library/bb763046(VS.100).aspx. This is also useful to get a practical example about implementing the MarshalByRefObject for dynamic code execution within application domains.
|
Conditional APTCA
You can allow an assembly to be called by partially trusted code by applying the System.Security.AllowPartiallyTrustedCallers attribute at the assembly level. This can be accomplished as follows:
Imports System.Security
<Assembly: AllowPartiallyTrustedCallers()>
Without this attribute, only
full-trusted code can call the assembly. Different from previous
versions, in the .NET Framework 4.0 this attribute no longer requires
an assembly to be signed with a strong name, and its presence involves
in the security checks all security functions present in the code.
Migrating from Old CAS-Based Code
If you move your existing
code to .NET Framework 4 and you made use of Code Access Security
policies, you might be advised with a message saying that CAS is
obsolete. In these particular situations you can add a specific section
to the application configuration file, which allows legacy policies
that look like this:
<configuration>
<runtime>
<NetFx40_LegacySecurityPolicy enabled="true"/>
</runtime>
</configuration>
Of course, you always need
to check if legacy policies are appropriate in the particular scenario
you are facing. The suggestion is to read the MSDN documentation about
CAS migration, available at http://msdn.microsoft.com/en-us/library/ee191568(vs.100).aspx.