Code Access Security Explained
Download code: Partial Trust Code Access Security – Explained
Let’s start with an over-simplified, imprecise explanation of CAS. (NOTE: this discussion assumes that all your assemblies are strong-named.) The first thing to do is explain the levels of trust. “Fully trusted” code can do whatever the user can do (which might be limited if the user is not using a Windows admin account). “No trust” code can’t do squat. Anything in between is called “partial trust.” Obviously, there are many degrees of partial trust – the code might have almost no privileges at all, or it might be able to do everything except reading the registry or something. My own, simplified, understanding is that there are three main ways of causing code to run with partial trust:
- If the assembly is loaded from a network share then it will run with the “LocalIntranet” permission set (by default this enforces a number of restrictions such as no registry, no file IO, limited reflection and so on).
- If your assembly refuses one or more permissions (eg you declare that you don’t want to be able to perform reflection) then your assembly is by definition partially trusted.
- You can configure your ASP.NET application to run with a particular trust level. In fact, Microsoft recommend running ASP.NET with “medium trust” if possible! This is defined in config files; “medium trust” means no registry, no reflection, no file IO outside your app’s virtual directory and a few other things).
Notice that an assembly might be fully trusted or partially trusted depending on the runtime circumstances. The assembly might be fully trusted if loaded from the C: drive, but will be partially trusted if loaded from a network share.
Next, a couple of handy definitions:
- Caller – Code that calls other code
- Callee – Code that is called by other code
So if method ClientMethod() calls method ServerMethod() then ClientMethod is the caller and ServerMethod is the callee.
If the calling method (the caller) is in an assembly that is running with partial trust then it is a “partially trusted caller.” In this case, if the callee’s assembly does not have the AllowPartiallyTrustedCallers (APTC) attribute then the called code will not run – regardless of the trust level of the callee’s assembly. You will get an exception.
And now for the demo. Obviously, there are many aspects to CAS; my idea is to show you a few basic ones to get you started. The demo has two strongly named assemblies: PartialTrustTest.exe and PartialTrustTestLib.dll. There is also a config file called PartialTrustTest.exe.config that is needed for one of the scenarios.
Scenario 1
Start by placing all the files together in a directory on your C: drive and running the exe. You will see a form with three buttons. They all do the same thing: they try to read the registry. The only difference is in the methods that they use to call the RegistryKey.OpenSubKey method:
- Read the registry (local assembly) – calls a method in the EXE
- Read the registry (from PartialTrustTestLib) – calls a method in the DLL (see code at bottom of email)
- Read the registry after asserting permission (from PartialTrustTestLib) – calls a method in the DLL that asserts the right to read the registry (I’ll explain this below, but note that it has nothing to do with the Asserts used in testing)
Click each of the buttons in turn; you should find that they all work fine. This is because both assemblies are fully trusted.
Scenario 2
To set up this scenario, follow these steps:
- Move the DLL to a network drive – let’s call that the U: drive and ensure you delete it from the C: drive so that we can be sure which one is being loaded!).
- Open PartialTrustTest.exe.config in Notepad and uncomment the
line. - Replace my username with yours in the
href attribute. - Now run the EXE.
Button 1 is still OK, but the other two buttons throw a SecurityException. This is because the DLL is now running with the LocalIntranet permission set and as such it can’t read the registry.
The APTC attribute is not relevant here because the caller is fully trusted (it is the callee that is partially trusted).
Scenario 3
This is the most interesting one. To set up this scenario, follow these steps:
- Delete PartialTrustTest.exe.config
- Move the EXE to your network drive (U: drive)
- Move the DLL back to your C: drive
- Open the .NET Framework 2.0 Configuration utility (under Administrative Tools) and add the DLL to your GAC.
- Now run the EXE
Note that the DLL has the APTC attribute, which it needs here because the exe is no longer running with full trust.
Button 1 fails, because the EXE is now running with the LocalIntranet permission set.
Button 2 fails. Why? The DLL is now fully trusted (it is in the GAC and it doesn’t refuse any permissions). But the EXE does not have RegistryPermission. This is an example of luring (the unprivileged EXE asks the privileged DLL to ask the .NET Framework, like a kid asking his older brother to buy beer on his behalf). To prevent this, the RegistryKey.OpenSubKey method demands that all its callers have RegistryPermission. This demand causes the CLR to do a ‘stack walk’, checking that each caller in the stack has the required permission.
Button 3 succeeds. This is because it calls a method in the DLL that ‘asserts’ RegistryPermission. Asserting a permission is like saying ‘trust me; I know what I’m doing: don’t worry if my caller(s) don’t have RegistryPermission because I have it and I promise not to do anything bad with it’.
Notice that the assertion didn’t help in scenario 2, because the DLL didn’t have RegistryPermission. Asserting a permission that you don’t have won’t get you anywhere.
To summarise:
| Scenario | EXE | DLL | Btn 1 | Btn 2 | Btn 3 |
|---|---|---|---|---|---|
| 1 | C: drive | C: drive | OK | OK | OK |
| 2 | C: drive | U: drive | OK | Fails – callee doesn’t have RegistryPermission | Fails – cal doesn’t have Registry Permission |
| 3 | U: drive | GAC | Fails – callee doesn’t have RegistryPermission | Fails – stack walk discovers that EXE doesn’t have RegistryPermission | OK – DLL asserts RegistryPermission |
| 4 | U: drive | C: drive (not GAC) | C: drive (not GAC) | No dice – you get an exception trying invoke the DLL; I think it’s not allowed in .NET | |
Using CAS correctly involves ensuring that your code can’t be used maliciously. Our DLL allows partially trusted callers and one of its methods asserts RegistryPermission. Once that DLL is installed in the GAC these two settings lower the security bar considerably. Any assembly that can run on our CLR can load our DLL and use our method to read the registry. Before adding the assertion we should (a) check our method carefully to make sure it can’t be used maliciously, and/or (b) apply extra restrictions to our method. The easiest way to restrict the method is to add a Demand to the method. This is where we demand that the callers meet a certain requirement (not the same one that we’re asserting, obviously – if they have that then there’s no point in asserting it!)
Here are the main actions associated with permissions:
- Assert – as explained above
- Demand – callee demands that all callers in the call stack meet a certain requirement
- Link Demand – callee demands that the immediate previous caller in the stack meet a certain requirement (faster than Demand, but less secure)
- Permission Request – this is where an assembly announces up-front the permissions that it needs in order to run (or those that would be nice to have). The alternative is to wait for runtime failure (as in my demo).
- Permission Refusal – assembly asks not be given a particular permission (because it does not need it – principle of least privilege). Refusing a permission will also make your assembly a partially trusted caller.
This article is already too long, but here’s one final point: don’t overuse Demand. For example in the demo there’s no need for our methods to demand RegistryPermission. We are calling the RegistryKey.OpenSubKey framework method, which already demands this permission. If we demand it ourselves then we are just forcing an extra stack walk that will hurt performance.
Links
The best articles I’ve found online are An Introduction to Code Access Security and Code Access Security in Practice.
Remember to remove the DLL from the GAC when you’re done.
Hope this helps. And have I got it right? Please let me know.
Code for button #2:
public static void TestRegistryAccess()
{
RegistryKey test = Registry.LocalMachine;
test.OpenSubKey("Software", true);
}
Code for button #3:
[RegistryPermission(SecurityAction.Assert,
Unrestricted=true)]
public static void AssertRegistryPermissionAndTestAccess()
{
RegistryKey test = Registry.LocalMachine;
test.OpenSubKey("Software", true);
}
Download code: Partial Trust Code Access Security – Explained

Great article Carl!
I did the scenarios and I now feel I have the basics down. Thanks for taking the time to write this. The MSDN stuff is so wordy, i find myself dozing off after paragraph 3. Your use of examples was spot on.
Cant comment on the accuracy (yet) ;)
Michael