6. Permission Enforcement
On earlier pages you have seen how to give permissions to assemblies. This raises another question: once an assembly has permissions, what does it use them for? Types in assemblies make demands for permissions. That is, a type knows that it uses some facility for which there is a .NET permission class and it demands that any code that calls the methods in the type has this specific permission. If the calling code does not have the permission the demand will throw an exception, which prevents the method from being called.
On this page you'll see how this works.
6.1 Types of Permissions
There are three types of permissions, CAS permissions, identity permissions
and non-CAS permissions. Non-CAS permissions do not perform a stack walk when
the permission is demanded, and so this type of permission is not based on the
identity of the current executing assembly, it is based on some other
evidence. In current versions of .NET the only non-CAS permission provided
with the framework is PrincipalPermission, this permission is
based on the identity of the current user (which clearly will be the same for
all assemblies in the call stack).
The examples on this page will be mainly about CAS permissions, in which case a demand is based on evidence from the current executing assembly and hence a demand will trigger a stack walk to check all assemblies in the call stack.
Identity permissions are also CAS permissions, but have some differences.
The framework defines several identity permission classes (eg
PublisherIdentityPermission and the version 3.0/2.0 permission,
GacIdentityPermission) but these are not granted through policy,
instead they are granted to an assembly based on the assembly itself. Identity
permissions have another property: you cannot create them in an unrestricted
state (SecurityAttribute.Unrestricted). This means that the
identity check will be performed, and the identity check can fail, if the
assembly has full trust (for example, an assembly could have full trust, but
if a demand was made against it for a certificate that it did not have -
PublisherIdentityPermission - the demand would fail). This makes no
sense, full trust should mean full trust.
|
.NET Version 3.0 In Version 3.0/2.0 of the framework this abnormality has been rectified, so demands for any identity permission will succeed if the assembly has full trust. |
6.2 Demanding Permissions
One permission is FileIOPermission, this is used to specify which
files and folders the assembly can access and what it can do with it. None of
the default security policies define a file permission (other than
Everything which grants access to all files and folders), this means that
assemblies that have full trust (the framework assemblies and assemblies that
are installed on the local computer) will have access to files subject only to NTFS
permissions, .NET makes no attempt to intervene. All other assemblies (those that are downloaded from the internet
or intranet) will not be allowed to access other files, because the
LocalIntranet and Internet permission sets will only give
access to files in isolated storage. In this example, you will develop a class that will need to access files in
a specific folder on your hard disk. This is the source for
lib.cs:
using System.IO;
using System.Reflection;
public class LibraryCode
{
const string folder = @"C:\SecureFolder";
public static string GetData(string str)
{
string strFile = folder + @"\" + str;
StreamReader sr = new StreamReader(strFile);
return sr.ReadToEnd();
}
}
A process can call this static method to get access to the a file in the
C:\SecureFolder folder. If the library and the process are installed on the
local machine then GetData will have access to the files on the hard disk
subject to NTFS access control lists. If the library is downloaded from the intranet then the
assembly will not have access to any folders and so a security exception is
thrown.
Now imagine that there is an assembly between the process and the library shown above:
![]()
The user.dll assembly can call LibraryCode.GetData
in lib.dll to access the file. lib.dll knows that it should be able to access files in the
C:\SecureFolder
folder, but if it is called by another library, does that library also have
the right permissions? If .NET blindly allowed lib.dll to access this folder
then the permissions accorded to lib.dll would be delegated to any assembly
calling it. Of course, .NET does not blindly give access to other assemblies.
Code access security involves checking the permissions that an assembly
has, but it also involves checking the permissions of every assembly in the
call stack. To investigate this, create a library called user.dll. The first
thing to do is create a key pair for the user.dll file so that
the public key for this assembly will be different to the public key for
lib.dll. Next, create a C# file called user.cs that has
following source code:
using System.Reflection;
public class UserCode
{
public static string GetString(string str)
{
return LibraryCode.GetData(str);
}
}
Finally, create a process (app.cs) that will call this code:
class App
{
static void Main(string[] args)
{
if (args.Length > 0)
{
Console.WriteLine("{0}", UserCode.GetString(args[0]));
}
}
}
Compile all of this code: app.exe depends on user.dll
and user.dll depends on lib.dll.
Now create the C:\SecureFolder folder and add a
text file to that folder. Now run the process to access that file. You'll find
that the contents of the file will be printed on the console.
Next, you'll address the issue of whether callers of lib.dll will have
access. To do this add the following highlighted lines:
public class LibraryCode
{
const string folder = @"C:\SecureFolder";
[FileIOPermission(SecurityAction.Demand, Read=folder)]
public static string GetData(string str)
(Note that StreamReader will perform its own demand for
permissions, as explained later, but we will apply this demand here so that
you can test permission demands. In most cases you will not need to apply such
a demand on your code.)
The GetData method has been decorated with the permission
attribute with the Demand parameter. This attribute indicates that the
caller of the method needs to have read
access to the folder, and furthermore, all other assemblies in the stack must
also have the permission (the method that makes the demand is not checked for
the permission).

Before LibraryCode.GetData is called the runtime inspects every method in the stack
starting at the method that called LibraryCode.GetData for
the permissions given to its assembly. If the runtime finds that an assembly
does not have the permission that is demanded then a security exception is
thrown. You can add more than one security attribute to a method in this way,
and if you do this you indicate that callers must have all of the permissions
demanded. If the caller lacks one of the permissions then a security exception
will be thrown.
Since user.dll is installed on the local machine it will have full
trust. Let's change that, by adding the following lines to user.cs.
using System.Security.Permissions;
public class UserCode
{
[FileIOPermission(SecurityAction.Deny, Unrestricted=true)]
public static string GetString(string str)
This code tells the runtime explicitly to deny the assembly the permission to access any file. When you compile all the code and run it, you'll find that a security exception is thrown.
at System.Security.SecurityRuntime.FrameDescSetHelper(FrameSecurityDescriptor secDesc, PermissionSet demandSet, PermissionSet& alteredDemandSet)
at LibraryCode.GetData(String str)
at UserCode.GetString(String str)
at UserCode.GetString(String str)
at App.Main(String[] args)
|
.NET Version 3.0 When you run this code on Version 3.0/2.0 of the framework you will get a more detailed exception stack trace: Unhandled Exception:
System.Security.SecurityException: Request failed. at LibraryCode.GetData(String str) at UserCode.GetString(String str) at App.Main(String[] args) The action that failed was: Demand The type of the first permission that failed was: System.Security.Permissions.FileIOPermission The first permission that failed was: <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="C:\SecureFolder"/> The trace indicates that the
failed call was due to the denied |
The reason this exception is thrown is that the
GetData method in lib.dll has demanded that it needs the
FileIOPermission, so a stack walk is initiated and this starts
with the immediate caller,
GetString in user.dll, since this method is denied
the permission an exception is thrown.
Note that since the demand stack walk starts at the
immediate caller it means that there is no point in making a demain in the
Main method of a process because this method has no caller. |
An assembly may not have a permission because of its evidence, but why
would a library deny itself a permission? Well, the code in a library will
know what code it wants to call and the actions it will take. UserCode.GetString
may make the decision that LibraryCode.GetData should be allowed to return data if it
was read from a database or read
from a socket, but it may take the policy decision that LibraryCode.GetData
should not access the file system. Note that you cannot deny the
SecurityPermissionFlag.SkipVerification because verification is
performed at JIT time but the denying of a permission is performed at runtime.
I should add a bit more
explanation about the demands that are made when you use FileStream.
In the code used so far, there are two lines of code that access the file (the
StreamReader constructor, and the call to ReadToEnd) this
code is provided by the framework library, which performs a demand for
FileIOPermission for the file that is being accessed, but only when
the file is opened. To test this out, comment out the declarative demand
on the GetData method, compile and run. Since GetString
in user.dll is denied access a security exception is thrown. The
interesting thing is the call stack:
System.IO.FileStream..ctor
System.IO.FileStream..ctor
System.IO.StreamReader..ctor
System.IO.StreamReader..ctor
LibraryCode.GetData
UserCode.GetString
UserCode.GetString
App.Main
This shows that the call to the StreamReader constructor in
GetData involves a call to a FileStream constructor
that makes a demand for the FileIOPermission for the requested
file, a stack walk is performed (starting at the caller of this method) until
it reaches GetString which is denied access to files in
C:\SecureFolder.
|
.NET Version 3.0 The call stack is slightly different if the same code is compiled for version 3.0/2.0 of the framework. Firstly, there is only one call to UserCode.GetString,
and secondly there is only one FileStream constructor but this
calls a method called Init that makes the demand. The exception
dump also indicates that the method that caused the failure was GetString,
and although it does not give the class name we know that it is a method on
UserCode. |
The
demand for the permission is made in the FileStream constructor
and not in the methods that actually read from the file. The
reason for this is performance. The argument is that a stack walk is expensive and if the constructor has
already
performed a stack walk there is no need to perform another one.
There is a problem here. A class, say, FileData, could open
the StreamReader object in its constructor and access the file in
another method. In this case you could have two other assemblies, one that
creates the FileData object which must have the appropriate
FileIOPermission permission and this class could pass the initialized
FileData object to another class in another assembly that does
not have the appropriate permissions. This is no problem because the second
class could call any method on FileData that accesses the
StreamReader object and no demands will be made.
The solution to this problem is to make sure that any method on
FileData that accesses the StreamReader object must have the demand for the
appropriate FileIOPermission permission.
Clean up the project by removing the [FileIOPermission]
attribute from
UserCode.GetString.
6.3 Imperative Demands
Demanding that permissions are available through attributes has the
advantage that you can use reflection to analyse an assembly and get a list of
the permissions demanded by the code in the assembly. The framework provides a tool called
permview that will do this for you and print the permissions on the console:
Microsoft (R) .NET Framework Permission Request Viewer.Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Method LibraryCode::GetData() Demand permission set:
<PermissionSet class="System.Security.PermissionSet" version="1">
<IPermission class="System.Security.Permissions.FileIOPermission,
mscorlib, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
version="1"
Read="C:\SecureFolder"/>
</PermissionSet>
This is a list of the declarative requests that your code makes, in other
words, this output shows that the LibraryCode.GetData method
requires that calling code must have the FileIOPermission
permission to accesses the specified folder. This does not necessarily tell
you the permissions required by code in the assembly, because the assembly you
examine could call another assembly that makes demands. However, it does give
you a starting point to determine the minimum permissions that the target
assembly requires to run.
The advantage with declarative demands is that the details of the demand is determined at compile time. This is also its disadvantage because it means that at compile time these details must be known and the assembly will be constrained to use these values. For example, in our code the path of the folder specified as the secure folder must be known at compile time and then it will be used forever more. Another disadvantage is that the granularity of the permission demand is the scope of the method.
If you want
to have a finer granularity you can use a permission object instead. Remove
the FileIOPermission attribute on LibraryCode.GetData and change the code
to this:
{
// Code here is not subject to the permission demand
// Determine the folder at runtime
string strFolder = folder;
FileIOPermission fio = new FileIOPermission(
FileIOPermissionAccess.Read, strFolder);
fio.Demand();
string strFile = folder + @"\" + str;
StreamReader sr = new StreamReader(strFile);
return sr.ReadToEnd();
}
The code determines the folder at runtime (it could read this from configuration information, for example). The code then specifically makes the demand. It is only at this specific point in the code that the stack walk will occur (starting at the immediate caller). Change the process entry point to look like this:
{
if (args.Length > 0)
{
if (args.Length > 1)
{
FileIOPermission fio = new FileIOPermission(
FileIOPermissionAccess.Read, @"C:\SecureFolder");
fio.Deny();
}
Console.WriteLine("{0}",
UserCode.GetString(args[0]));
if (args.Length > 1)
{
FileIOPermission.RevertDeny();
}
}
}
(Also add a using statement at the top of the file for
System.Security.Permissions.) Compile lib.cs, then compile
user.cs (which is
dependent upon lib.dll) and finally, compile app.cs (which is dependent upon user.dll).
The idea is that if you provide more than one command line argument the
read permission will be denied (regardless of what the command line argument
is!). This denial only lasts until RevertDeny is called. Now you should be
able to test the code by passing just the name of a file (to get access)
or the name of the file and another parameter (to be denied access).
Note that if a method needs to demand more than one permission it can do
this by creating a PermissionSet object and each permission can be added
through a call to AddPermission and the Demand can be performed on the
permission set.
6.4 Assert Demands
Denying permissions through a stack walk is powerful. It protects the machine from possibly dangerous actions requested from code from an unknown source. However, a stack walk is not without cost, and many calls to code that trigger a stack walk can impeded performance. For this reason CAS gives you the ability to assert a demand.
Code that performs some action that requires a CAS permission can call the
Assert method on that permission object which will perform a
check on the assembly that called Assert, but will not perform a
stack walk, so other assemblies in the call stack will not be checked. This
looks like a hole in CAS but it isn't for two reasons: firstly the assembly
making the assert will be checked for the asserted permission and so this is
not a mechanism where a rogue assembly can prevent CAS checks for permissions
it does not have; secondly, the assembly must have the permission to call
assert (the default policy gives this permission to intranet, but not internet
code).
One side effect is that calling code does not have the permission that is asserted, so if you call assert you must be careful about the actions being taken. When you assert a permission you are effectively saying that any code could have access and so do not assert a permission that could allow sensitive information to leak.
For example, imagine our SecureFolder actually has two types
of files, secret and public. The public files have names that start with 'public'.
Now imagine that CAS policy denies an assembly ccess to the file
system (in our example code app.exe does this through a call to
Deny), this will mean that
calls from the assembly will be denied
access regardless of the type of file it is accessing. The LibraryCode
in lib.dll can apply a more intelligent solution by examining the
request and determine whether the call is to a
restricted file or not. If the call is to a public file then
LibraryCode.GetData can assert the file permission. This means that
if lib.dll has the permission to make the assert, and it has
the file permission, then a stack walk for the file permission will stop at
lib.dll and the call to read the file will succeed even if an
assembly higher in the stack does not have the permission.
Add the following code to LibraryCode.GetData.
FileIOPermissionAccess.Read, strFolder);
if (str.IndexOf("public" == 0)
fio.Assert();
else
fio.Demand();
string strFile = folder + "\\" + str;
Notice that this makes a proactive decision over the assertion: it will only assert the permission if it can positively determine that the assertion will not cause a security issue.
Create two files in SecureFolder
called public_data.txt and secret_data.txt. Bearing
in mind that the test application called with just one parameter will type the
file indicated, but the application called with two parameters will deny the
FileIOPermission, try these tests:
app public_data.txt deny
app.secret_data.txt
app secret_data.txt deny
You will find that only the last test will throw a security exception: all
of the
others will access the file. Calling the test application with two parameters
simulates the occasion when a calling assembly does not have the
FileIOPermission, and calling it with a single parameter simulates the
calling assembly having the permission. Thus the results from the last two
tests are understandable: in the first of the two the calling assembly has the
permission, but in the second of the two the calling assembly does not have
the permission and hence the security exception is thrown. The first test is
also easily understandable: just one parameter is passed and so the calling
assembly has the FileIOPermission. The second test is the
interesting one, because in this test app.exe is denied the
permission, but since it requests a 'safe' file, LibraryCode.GetData
asserts this permission, preventing a stack walk from examining the
permissions for app.exe.
Note that lib.dll does not get any special permissions. This
assembly is granted FileIOPermission by policy because it is in
the MyComputer zone and so has FullTrust.
lib.dll is
asserting that it wants to use those permissions regardless of the permissions
granted to assemblies higher in the stack. IStackWalk.Assert does
not give the assembly generating the assert any more permissions, but it
effectively elevates the permissions of the calling assemblies because a stack
walk demand that would have failed is not made. |
A common example of asserting permissions is the
SecurityPermissionFlags.UnmanagedCode permission that is required to call
native code. Many of the framework classes need to call Win32 native code but
it would be impractical to require all assemblies to have this permission.
Code that calls Assert must be granted the
SecurityPermissionFlags.Assertion permission and this is verified
at link time through a link demand.
6.5 Link Demands
Review the code that we have seen so far: with imperative demands the
exception is thrown when the Demand is made. With declarative demands the
demand is made when the method is called or (if the attribute is added to the
class) when the class is accessed. In all of these cases the assembly has
already been
loaded and JIT compiled. .NET security allows us to go a couple of steps
further. In this section we'll see that the runtime can make checks at JIT
compile time, and in the next section we'll see that the assembly loader
(Fusion) can also perform checks.
The JIT compiler can perform code access security checks while it is compiling
IL. It checks to see if the code being
compiled calls code that has code access security attributes that request a
SecurityAction.LinkDemand permission check. This check is similar to
Demand in that
a permission is demanded, however, the demand is only made on the immediate
caller and not on the entire stack. Also, unlike Demand, where the check is made
at runtime and every time the method is called, LinkDemand is
only made at link
time, that is, during JIT compilation.
Note the order: the JIT compiler checks to see if the methods called by the
current have a LinkDemand and if so it performs the check
on the current assembly to see if it has the required permission.
|
This makes LinkDemand much cheaper to use than Demand, however, because the security
check is shallow it is not as safe as Demand. The LinkDemand action is ideal
for use with identity checks like [PublisherIdentityPermission],
because it checks that the direct caller of the current assembly has the appropriate
identity (in this case, a certificate). ([StrongNameIdentityPermission] is also an identity
check, however as I explained earlier you
should not put any trust in strong names in v1.0 and v1.1 of the framework
because there is a bug in how strong names are validated. Furthermore, a
cracker can provide a spoofed strong name through the evidence collection of
code that calls a library and hence the identity check will succeed even
though the caller does not have the specified strong name. For this reason the
identity permission has no effect in version 3.0/2.0.)
To test this out, edit LibraryCode.GetData so that it no
longer uses imperative security, and instead it uses LinkDemand:
public static string GetData(string str)
{
Console.WriteLine("UserCode.GetData");
string strFile = folder + @"\" + str;
StreamReader sr = new StreamReader(strFile);
return sr.ReadToEnd();
}
You have added a call to WriteLine so that you can see when
the exception is thrown. The Main method of app.exe
will allow you to determine if the FileIOPermission is denied,
but let's do this explicitly in the user.dll assembly. Edit user.cs and add the
highlighted lines:
[assembly: FileIOPermission(SecurityAction.RequestRefuse,
Unrestricted=true)]
public class UserCode
{
public static string GetString(string str)
{
Console.WriteLine("UserCode.GetString");
Again there is a WriteLine to indicate that this code has been
called. The attribute is applied at assembly level and uses SecurityAction.RequestRefuse
which means that when policy determines the permissions that it will grant to
the user.dll
assembly it will not get file permissions (and hence user.dll
becomes a partially trusted assembly). Setting the
Unrestricted property to true means that no file can be
accessed. It is worthwhile adding a message in the
Main
function:
{
Console.WriteLine("App.Main");
Compile all the code and run it requesting a file. You'll find that a SecurityException
will be thrown regardless of how many parameters you pass to the process. You will get
the following:
Unhandled Exception: System.Security.SecurityException: Request for the permission of type System.Security.Permissions.FileIOPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.
at UserCode.GetString(String str)
at App.Main(String[] args)
The state of the failed permission was:
<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Read="C:\SecureFolder"/>
Here, you'll see that the message
App.Main appears on the console, but the message UserCode.GetString
does not appear. This confirms what I mentioned above: when UserCode.GetString
is JIT compiled the JIT compiler checks the methods that UserCode.GetString will call,
if those called methods have a LinkDemand a permission check is performed
on user.dll. If this check fails then JIT compilation of UserCode.GetString
will fail.
This is important: the caller code is not JIT compiled if the
called code has a LinkDemand and the caller assembly does
not have the permission.
|
|
.NET Version 3.0 The exception trace for this code on version 3.0/2.0 is different and more elucidating (this is edited): App.Main
Unhandled Exception: System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. [snip] at UserCode.GetString(String str) at App.Main(String[] args) The action that failed was: LinkDemand Again, this indicates that the code in |
MSDN is a bit confusing on this issue. On the one hand the Developer's Guide says this:
Linking occurs when your code is bound to a type reference, including function pointer references and method calls. If the caller does not have sufficient permission to link to your code, the link is not allowed and a runtime exception is thrown when the code is loaded and run.
This text says that the link is not allowed if the caller does
not have sufficient permissions, that is, the caller will throw the
exception and will not be executed. This part of MSDN is explicit and clear,
however, many other places are not so clear. For example the
MSDN documentation for SecurityAction says this for
LinkDemand:
The immediate caller is required to have been granted the specified permission.
It is easy to interpret this as meaning when a method that has an attribute
with LinkDemand is JIT compiled its immediate caller is checked.
This is not the case, it is the other way round.
If you have already worked through my Fusion
tutorial you'll remember that an assembly can be pre-jitted by the
ngen utility. Normally JIT compilation will be applied on a
method by method basis performed just before the first time that a method is
called (the result is cached in memory and is used during later calls to the
method). When you pre-jit the assembly (to create a native image) the
JIT compiler will be run over every method in the assembly. If there are any
link demands then these are performed against the policy defined at link time,
which will be when ngen is called. This is one reason why you
cannot pre-JIT an assembly on your machine and then install the native image
on another machine: you cannot guarantee that the policies will be the same
for both machines. Furthermore, even if you do generate the native image on
the target machine the link demands will be affected by any changes in the
security policy. If the new policy is a superset of the policy when the native
image was generated then the runtime will ignore the native image and it will
use the IL image and JIT compile it as normal. Thus if you change policy on a
machine then you may need to ngen all of your native images.
|
.NET Version 3.0 Finally, it is important to realise that in version 3.0/2.0 of the runtime link demands are optimized away when the assembly has full trust because such demands should always succeed (full trust, should mean that). In version 1.1 of the framework a link demand will be performed, and this would mean that, for example, a link demand for strong name identity could fail if a full trust calling assembly did not have the strong name, which is a contradiction to what full trust means. |
Note that when you make a call to a public (or protected) method on a
public class in a strong named assembly then there will be an implicit link
demand for FullTrust, more details are given in a
later section.
To clean up this example, remove the FileIOPermission
attribute from user.cs and the LinkDemand from
lib.cs.
6.6 Requested Permissions
We established earlier on that by the time that an assembly is loaded the runtime would be assured that the assembly has not been tampered or corrupted (through validation), and that it does not attempt to anything that is unsafe (through verification). In addition, when a class is loaded, or a method is JIT compiled, or a method is run, a security check is performed against the permissions granted by policy to the assembly. If any of these checks fail a security exception is thrown. However, the assembly will be loaded and in some cases code in the assembly will have been executed. The code that has executed will have the correct permissions, or perform some low privileged action that does not require elevated permissions. In this case the code is most likely initializing code ready to perform the privileged operation at a later time. You could argue that if the eventual operation will fail there is no point performing the initialization.
.NET allows you to mark an assembly with the minimum permissions that must be granted for all code in the assembly to run. The implication is that if the assembly is not granted those permissions then some code in the assembly will not run, so the assembly indicates to the runtime that no code should run. Thus, the runtime will not load the assembly.
|
.NET Version 3.0 You can use the permcalc tool, described
earlier, to determine the minimum permissions that your assembly needs to
run. |
There are three SecurityAction members that you can use to do
this, and
we have already seen one in use. RequestMinimum is
used for permissions that your assembly must have to run, RequestOptional are
the permissions that your assembly would like to have, but would work without, and RequestRefuse are the permissions that the assembly does not want.
However, note that RequestOptional cannot be used without RequestMinimum
requests because RequestOptional refuses all other permissions,
which I will return to in a moment. When the assembly is loaded policy is applied to determine the permissions
that the assembly is granted; the runtime then checks that the permission set
contains the permissions that the assembly specifies are RequestMinimum.
If this is the case, then the runtime calculates the union of the RequestMinimum
and RequestOptional permissions and intersects this with the
granted permissions (since it is an intersection it means that no extra
permissions are granted in this way). Finally, the permissions that are RequestRefuse
are removed from the permission set. This then gives the final permissions
granted to the assembly.
For example, lib.dll could have this attribute:
It is good practice to give an assembly a minimum permission set.
RequestOptional is interesting and curious. To test how such a
request works create a new file
called requested.cs:
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
[assembly: FileIOPermission(SecurityAction.RequestOptional, Read =
@"c:\SecureFolder\public_data.txt")]
[assembly: FileIOPermission(SecurityAction.RequestOptional, Read =
@"c:\SecureFolder\secret_data.txt")]
class App
{
const string folder = @"C:\SecureFolder";
static void Main()
{
try
{
string str = GetString("public_data.txt");
Console.WriteLine(str);
str = GetString("secret_data.txt");
Console.WriteLine(str);
}
catch(SecurityException e)
{
Console.WriteLine(e.Message);
}
}
static string GetString(string str)
{
string strFile = folder + @"\" + str;
StreamReader sr = new StreamReader(strFile);
return sr.ReadToEnd();
}
}
This code accesses two files from the SecureFolder directory.
Make sure that you have this folder and you have these two files. All the code
does is open the files and print each file's contents to the console. Although
this assembly appears to require access to these two files I have used
RequestOptional, this is not a contradiction because the code
handles the exception thrown if the permission is not granted, so you can
argue that the application will runt to completion whether or not the
permission is granted. If the exception handling code was not present then
permission to access these files is required because the application will not
complete if the application cannot access one of these files.
Compile this
code and run it; you'll find that it will run as expected and the files will
be accessed. Comment out both attributes, compile and run the code. Again
you'll find that the code will run. This is understandable because the
assembly is in the MyComputer zone and so it will have
FullTrust.
Now change the code so that only the second of the request attributes (the one for
secret_data.txt) is commented. Recompile and run the code, here are the results I
get (v1.1):
Request for the permission of type System.Security.Permissions.FileIOPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.
The first line indicates that the file public_data.txt was
read, which is understandable because you made an optional request that the
read file permission is in the granted permission set; but the following lines
are surprising. The name of the request would suggest that the request is optional: 'load
this assembly whether or not this permission is granted'. However, this is not
the case. The request means that the assembly would like the permission to be
granted by policy (and you can only get the permission through policy), but it
also indicates that all other permissions should be removed. Thus if you have
this request, you indicate that this is the only permission you want.
Since RequestOptional will refuse all other permissions except
the permission mentioned it really should have been called something like
RequestExclusive. |
If your code requires any more permissions then you should add a RequestOptional
for that permission or a
RequestMinimum.
In summary: if you do not use RequestOptional then the final permissions are the
intersection of the
RequestMinimum and
the policy granted permissions, reduced by RequestRefuse. If you
use RequestOptional then the final permissions are the union of the
RequestMinimum and
the RequestOptional permissions, reduced by RequestRefuse.
However, note that if the assembly does not have the permission specified by
RequestMinimum then the assembly will not be loaded; but the RequestOptional
and RequestRefuse will not prevent the assembly from being
loaded, instead, they affect the permission set used when the assembly
executes.
6.7 Inheritance
In .NET the assembly is the security boundary, that is, permissions are given to an assembly and different assemblies will have different permissions. .NET allows a class in one assembly to derive from a class in another assembly. This means that the base class could perform some privileged operation and the assembly will have the evidence to be granted those permissions. An attacker could decide to exploit this by creating a class that derives from the privileged class, the reasoning goes like this: the base class assembly has the evidence that gives it the elevated permissions, but the attacker's assembly has evidence that gives it restricted permissions but the derived class can override methods of the base class that will have security demands and replace those methods with code that does not have the demands.
There are a couple of ways to prevent this. The first is the simplest: make
the privileged class sealed so that it cannot be used as a base class. The
second is to mark the class with an inheritance demand. This indicates that
the current class requires the specified permissions and that any class that
derives from it should also have the required permissions. Thus, if a derived
class is declared in another assembly it means that the other assembly must
have the
required permission.
To do this, you use a permission attribute on the class and use a
SecurityAction of InheritanceDemand. To illustrate this
you will have to have three assemblies: a process assembly that uses a class
in a library, and this class derives from another class in another library.
Create a file called lib.cs and add this code:
using System.Security.Permissions;
public class PrintData
{
protected string data = "secret value";
[UIPermission(SecurityAction.Demand, Window=UIPermissionWindow.AllWindows)]
public virtual void PrintString()
{
Console.WriteLine(data);
}
}
This code initializes a protected field and returns this value in
the PrintString method. A caller of this method must
have an appropriate UI permission. (I have chosen [UIPermission]
attribute because the method is accessing the UI through the console.) Now let's take this a step further and derive from the class. Create a new file called
data.cs and add this code:
using System.Security.Permissions;
[assembly: UIPermission(SecurityAction.RequestRefuse, Window=UIPermissionWindow.AllWindows)]
public class PrintDataDerived : PrintData
{
public override void printString()
{
Console.WriteLine(data);
}
}
PrintString so that it does not
have the permission demand. I have added the RequestRefuse to
ensure that this assembly does not have the appropriate permission. The
application code (app.cs) looks like this:
class App
{
static void Main()
{
Console.WriteLine("App.Main");
PrintDataDerived pd = new PrintDataDerived();
pd.PrintString();
}
}
PrintDataDerived class and hence gets
access to the PrintString method regardless of the permissions
that data.dll has. Compile this code: note that the application
must have references to both of the libraries because the constructor call in
app.exe needs to have access to both the base class and the
derived class.
csc /t:library data.cs /r:lib.dll
csc app.cs /r:data.dll /r:lib.dll
If you run the application you'll find that the data is printed on the command line. The secret is out!
Now change the attribute on PrintData.PrintString on the
base
class.
public virtual void PrintString()
This indicates that if the method is overridden in a derived class in another assembly then that assembly must have the required permission. Once you have compiled this, run the application, and you'll find that a security exception is thrown:
at App.Main()
The state of the failed permission was:
<IPermission class="System.Security.Permissions.UIPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Window="AllWindows"/>
This shows that the exception is thrown before App.Main is
called because the message in Main is not printed on the command
line. The reason is that when Main is JIT compiled the JIT compiler must have
access to both PrintDataDerived and its base class PrintData.
The JIT compiler sees that PrintData has the
InheritanceDemand and so it checks the permissions granted to
data.dll which results in the exception.
|
.NET Version 3.0 The exception is more explicit in version 3.0/2.0 of the runtime (edited trace): Unhandled Exception:
System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.UIPermission,
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
failed. [snip] The action that failed was: InheritanceDemand The type of the first permission that failed was: System.Security.Permissions.UIPermission The first permission that failed was: <IPermission class="System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Window="AllWindows"/> |
A security attribute with InheritanceDemand can be applied to
a class or a virtual method (it is meaningless on a non-virtual method). This
attribute is transitive, that is, if it is applied to a base class (or a base
class method) then it also applies to all classes in the inheritance chain,
not just the immediately derived class.
6.8 Partially Trusted Code
Fully trusted code gets all permissions and code that is installed on your
machine will get the FullTrust permission set. In addition, the
framework assemblies get full trust, not only because it is installed on the
local machine, but also because assemblies that have the Microsoft strong name
(and assemblies with the ECMA strong name) also get full trust.
|
.NET Version 3.0 In version 3.0/2.0 of the framework, any assembly that is installed in the GAC will get full trust. There are several reasons for this. Firstly, the physical NTFS folders that are used by the GAC have Access Control Lists that restrict the operations of adding assemblies to the GAC to administrators. An administrator is a highly trusted person and so anything they install into the GAC should also be trusted. This is reflected in the special treatment of GAC assemblies, for example, once an assembly has been installed in the GAC strong name is never performed on it when it is loaded by another assembly. |
Code that does not get full trust will have partial trust. Partial trust code will have to call framework code, and hence an assembly with full trust will be called by an assembly with partial trust.
In the first version of the .NET framework there was an implicit link
demand for FullTrust on every public or private method on
public types in a library with a strong name. This meant that an assembly with
a strong name could only be called by assemblies that had full trust. If
an assembly was downloaded from another machine then the assembly will have
partial trust which is the default policy, however, since the assembly was downloaded from another machine
it means that the assembly must have a
strong name. In this case a partially trusted assembly will
implicitly link demand that the assemblies that call it have full trust. The
reason for this is to prevent a luring attack, that is, an assembly
with partial trust calls a fully trusted assembly that performs some
privileged action (perhaps code that should, but doesn't, make a demand, or
maybe code that asserts a permission). This implicit link demand will prevent
such an attack.
This implicit link demand was relaxed in version 1.1 with the
assembly-level custom attribute (ie, not a security attribute) called
[AllowPartiallyTrustedCallers]. Assemblies with this attribute can be
called by partially trusted assemblies and the implicit link demand is not
called. You should only apply this attribute after you have carefully checked
that the code does not do something that is privileged and is not protected by
a demand, and you should check that there are no asserts that can be exploited
by the caller. This attribute does not relax code access security because
any demands that you apply (link demands or stack-walk demands), or are
applied by the code you call will still occur.
For example, use the code developed during the early parts of
this page. The process file (app.cs)
calls an intermediate library (user.cs), which then calls the
final library (lib.cs). The process takes the name of a file to
use, but if more than one parameters are passed on the command line then the
process will deny the FileIOPermission effectively preventing the
file access. We won't use that facility in this test. Instead, give the final
assembly (lib.dll) a strong name:
public class LibraryCode
(If you use C# 8.0 then use the command line switch /keyfile:key.snk.)
Next, make the calling assembly have partial trust by adding the following:
public class UserCode
Compile all the code and run the process requesting a file. You should get a result like this (edited):
Unhandled Exception: System.Security.SecurityException: Security error.
at UserCode.GetString(String str)
at App.Main(String[] args)
This is misleading, the code should work because no demand was made for the permission that was refused.
|
.NET Version 3.0 In version 3.0/2.0 of the framework, any assembly that is installed in the GAC will get full trust. App.Main
Unhandled Exception: System.Security.SecurityException: That assembly does not allow partially trusted callers. at System.Security.CodeAccessSecurityEngine.ThrowSecurityException(Assembly asm, PermissionSet granted, PermissionSet refused, RuntimeMethodHandle rmh, SecurityAction action, Object demand, IPermission permThatFailed) at UserCode.GetString(String str) at App.Main(String[] args) The action that failed was: LinkDemand This specifically shows that a |
Remove the strong name on lib.dll (in C# 7.0 comment out the
attribute). Now you will find that the code works. Clearly the problem is that
the RequestRefuse makes user.dll a partially trusted
assembly and this called a strong named assembly.
Replace the strong name and add the
[AllowPartiallyTrustedCallers] assembly attribute:
using System.Security;
[assembly: AssemblyKeyFile("key.snk")]
[assembly: AllowPartiallyTrustedCallers]
Again, compile all of the code and then run the process requesting a file. This time you will find that the process will work as expected.
6.9 Transparencyy
Code Access Security gets a huge amount of power from the stack walk. When a demand is made, CAS walks the stack to ensure that all assemblies in the stack has the demanded permission. If one assembly does not have the permission then a security exception is thrown and the code that demanded the permission is not run. A link demand curtails this mechanism because this demand will only solicit a check on the caller to see if it has the demanded permission. The stack walk can be prevented entirely with an assert: the code that asserts the permission must have the permission, but code that calls it does not have to have the permission. In effect, link demands and asserts elevate the permissions of calling code, because code further up in the call stack will not have to have a required permission. In many cases this is by design, but it does mean that you have to check very carefully that you really do want partially trusted code to perform the operation.
Transparency is a feature of version 3.0/2.0 of the framework that attempts to resolve the problems outlined above. Transparent code voluntarily relinquishes permissions if its callers have less permissions. Transparent code follows these rules:
- cannot assert for permissions and stop a stack walk
- cannot satisfy a link demand, and so link demands are converted into normal stack-walk demands
- cannot automatically use unverifiable code even if the code has
SkipVerification, instead a stack walk demand is made forUnmanagedCode - cannot automatically call platform invoke code with
[SuppressUnmanagedCode], instead a stack walk demand is made forUnmanagedCode
Code will never get more permissions through transparency, it will get the
permissions granted by policy or the permissions of its caller, whichever is
the less. An assembly is marked as transparent with the assembly level
attribute, [SecurityTransparent].
Note that a transparent assembly can have
[AllowPartiallyTrustedCallers] in which case lower-trust assemblies can
call its types under that lower trust, or it may not. Similarly, a strong
named assembly with
[AllowPartiallyTrustedCallers] may be transparent (in which case the
types will be called using the permissions of the caller) or it may not, in
which case it can call Assert and its code will be called with
the higher permissions of the strong named assembly. You have a range of
choices.
For example, examine the following diagram:

Here, the process loads a partially trusted library assembly called
caller. caller calls trans which then calls lower,
both of which are fully trusted. lower.cs looks like this:
using System.Security;
using System.Security.Permissions;
[assembly: AllowPartiallyTrustedCallers]
public class Lower
{
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public void CallLower()
{
}
}
You should not write code like this, there is a general rule that you
should not expose a method that has a link demand because this demand will not
cause a full stack walk, all it will do is cause a JIT check on the caller. In
this case that means that trans will be checked for full trust, which will
succeed. This assembly has a strong name because trans will have a strong
name, and a strong named assembly can only call strong named assemblies.
You'll see the reason for [AllowPartialTrustedCallers] later.
trans.cs looks like this:
using System.Security;
[assembly: AllowPartiallyTrustedCallers]
public class Trans
{
public void CallTrans()
{
Lower lower = new Lower();
lower.CallLower();
}
}
The only point to make about this is that it has [AllowPartialTrustedCallers]
because its caller will be partially trusted.
caller.cs is
similarly simple:
public class Caller
{
public void CallCaller()
{
Trans trans = new Trans();
trans.CallTrans();
}
}
Finally the process, app.cs:
using System.Security;
class App
{
static void Main()
{
try
{
Caller caller = new Caller();
caller.CallCaller();
}
catch (SecurityException se)
{
Console.WriteLine(se.ToString());
}
}
}
Since this is demonstrating a feature that is only available on .NET 3.0/2.0 we
will apply the strong name through the command line. The three libraries
require a strong name, so create a key file with
sn -k key.snk
and then use the following command lines:
csc /t:library trans.cs /keyfile:key.snk /r:lower.dll
csc /t:library caller.cs /keyfile:key.snk /r:trans.dll
csc app.cs /r:caller.dll
Run the process and you'll find that it will run with no exceptions. The
reason for this is because all the assemblies will have full trust and so the
link demand in lower will succeed because trans will have full
trust. We want to simulate caller getting a partial trust by
being downloaded from the intranet. To do this you'll use the technique shown
in the Fusion workshop by installing
the library within IIS on the local machine and then using a configuration
file to specify a code base. The assembly that is downloaded must have a
strong name, which is why caller has to be signed. First create a folder under the root web folder,
then move the library to the new folder:
move caller \Inetpub\wwwroot\bin
The easiest way to create the configuration file is to use the configuration
tool. Under My Computer is Assemblies. Right click on this and
select Add, then click on Other and browse for the application
process. Click Open then OK. Now click on Configured Assemblies,
right click and select Add. Select Choose and assembly from the list
of assemblies this application uses, click on Choose Assembly and
select caller and click on Select and click on Finished.
The property dialog for the assembly will show, so select the Codebase
tab. In the Requested Version type 0.0.0.0 then next to it in the
URI column type http://localhost/bin/caller.dll; click on OK.
In effect, this will create the following
configuration file:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="caller" publicKeyToken="caec91f465edcaba" />
<publisherPolicy apply="yes" />
<codeBase version="0.0.0.0" href="http://localhost/bin/caller.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Now when you run the example the caller assembly will be
downloaded from the website and put in the download cache. Run the code.
You'll find that no exception has been thrown.
The problem here is that lower performs a link demand for full
trust which is satisfied by trans, but caller only
has partial trust and yet can call an assembly that can perform a privileged
task. The trust of caller has been elevated by the link demand.
Now change trans.cs to make the
security transparent:
[assembly: SecurityTransparent]
Since the version of trans does not change, and neither does
its public interface, you can simply compile trans.cs and you do
not need to build caller.dll, nor app.exe. Now run
the application, you'll find that the following exception will be thrown:
at Trans.CallTrans()
at Caller.CallCaller()
at App.Main()
The action that failed was:
Demand
Notice that the problem is that a demand failed, not a link demand. The
reason is that in response to the [SecurityTransparent] attribute
the runtime converts all link demands to full demands.
Code that isn't transparent is called security critical and is the
default for current versions of the framework. In some cases you may want some
code in the assembly to be transparent and other code to be critical. The way
to do this is a little odd. You start by marking the entire assembly with the
[SecurityCritical] attribute that indicates that by default code
is transparent (yes, that is not a typo) but that some of the code can
be security critical. Contrast this with [SecurityTransparent]
which indicates that the entire assembly has transparent code and contains no
critical code. Edit trans.cs
and change [SecurityTransparent]
to
[SecurityCritical], recompile and re-run the application to
confirm that the link demand is converted to a demand.
Once you have marked an assembly with [[SecurityCritical]
you use this same attribute on types or methods that you want to indicate as
being security critical. If you want to mark an entire type as being critical,
you do so with the [SecurityCritical] attribute, passing
SecurityCriticalScope.Everything. Now edit the trans.cs
to include the following method, and call this method in
CallTrans (remember to add a
using statement for System.Security.Permissions):
{
Critical();
Lower lower = new Lower();
lower.CallLower();
}
public void Critical()
{
SecurityPermission sp = new SecurityPermission(PermissionState.Unrestricted);
sp.Assert();
}
Run this code. You will find that you will get the following exception:
This confirms that the runtime is enforcing the rules of transparent code.
To make this code work add [SecurityCritical] to the
Critical method. Compile the method and run the application and this
time you'll find that the Critical method will be called with no
exception, but you will still get an exception from CallLower (if
you chose to, you can comment out the call to CallLower so that
the exception will not be thrown.).
Transparent code has modified .NET visibility rules. Normally, critical
code (that is, the default in current versions of .NET) can call members on
other classes in the assembly that have the assembly metadata
attribute (that is, marked with internal in C#) or protected and
private methods on the same class. However,
transparent code can only call critical code that are
public. The framework does provide a solution to this issue, the [SecurityTreatAsSafe]
attribute. When you apply this to a critical member with assembly
accessibility you can call this member from a method marked as transparent
code.
To test this out, make Critical a private method, compile
trans.cs and run the application; you'll get the following
method:
at Trans.CallTrans()
at Caller.CallCaller()
at App.Main()
This exception shows that CallTrans does not have the access
permission to call the Critical method: but they are methods
within the same class! To resolve this issue add the [SecurityTreatAsSafe]
to Critical; compile and run to confirm that the method can be
called.
| I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com. |
Errata
If you see an error on this page, please contact me and I will fix the problem.