New Security Model: Moving to a Better Sandbox
For .Net Framework 4, we decided to remove the dependency on caspol and the policy levels and make things simpler.
With this change, the default grant-set for assemblies is now FullTrust unless the host (such as InternetExplorer) decides to load them in a sanbox. We also made CodeAccessPermission.Deny obsolete. This MSDN article describes the changes we made in the security namespace for .Net Framework 4 in detail.
For most people, this change in policy will be unnoticeable, their program probably was already running in full-trust and the world is the same. For some others, things will be a lot easier: launching their tool from the companies’ intranet share is now possible without the need to change the .Net Framework policy. For an even smaller set of users, there will be some issues that they will encounter, they were probably expecting assemblies to be loaded as partial trust and now they are full-trust.
In this post, we will cover how programs can be migrated to the newer security model and still work in the way they were intended to work.
In versions of .Net Framework before v4, we had many ways to restrict the permissions of an assembly or even certain code path within the assembly:
1. Stack-walk modifiers: Deny, PermitOnly
2. Assembly-level requests: RequestOptional, RequestRefuse, RequestMinimum
3. Policy changes: caspol, and AppDomain.SetPolicyLevel
4. Loading an assembly with a Zone other than MyComputer
In the past, these APIs have been a source of confusion for host and application writers. In .Net Framework 4, these methods of restricting permissions are marked obsolete and we hope to remove them at a point in the future. The .Net Framework 4 throws NotSupportedException when encountering calls to functions allowing any of these sandboxing methods. Applications that used these sandboxing APIs will now see an exception similar to this:
System.NotSupportedException: The Deny stack modifier has been obsoleted by the .NET Framework. Please see http://go2.microsoft.com/fwlink/?LinkId=131738 for more information.
Here is what you can do, after identifying that your code uses one of the previously described sandboxing methods:
1. Execute the partial trust code inside a partial-trust AppDomains. This approach might appear difficult because it asks you to figure out what trust levels your application needs. Having part of your application running in another AppDomain also requires some consideration about how to do the communication with the objects residing in the new AppDomain. This model might be more complex than just a command that changes the machine policy, but we think it is a better one. This article provides more information about why this sandboxing strategy is better.
Here are the steps for creating a new sandboxing AppDomain:
1.1. Remove the Deny, assembly level requests or the caspol command from your application.
1.2. Create a new partial-trust AppDomain with a partial-trust grant set. This is done by calling the override for AppDomain.CreateDomain that receives a grant-set and a full-trust StrongName list:
public static AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info, PermissionSet grantSet, params StrongName fullTrustAssemblies)
The MSDN article talking about this specific override is located here
This would create a new AppDomain which would give assemblies by default the PermissionSet given as a parameter. Assemblies that have their StrongName present in the fullTrustAssemblies parameter, will receive FullTrust as the grant-set.
1.3. Create an instance of one of your classes inside the new AppDomain. Your class would have to inherit from MarshalByRefObject. The API to use is this:
public object CreateInstanceAndUnwrap(string assemblyName, string typeName)
The MSDN article talking about this specific override is located here.
This API will return you a reference of type object to an instance of a class inside the new AppDomain. You would have to convert that instance to your specific type, so you would be able to call functions present in your class on it.
1.4. Call a function on your newly created instance. This function call would be marshaled across AppDomain boundaries and will be executing in the new AppDomain, thus in partial trust.
PermissionSet ps = new PermissionSet(PermissionState.None);
//Create a new sandboxed domain
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
AppDomain newDomain = AppDomain.CreateDomain(“test domain”, null, setup, ps);
//Create an instance in the new domain.
//The class has to derive from MarshalByRefObject. We consider
//PartialTrustTest to be such a class
PartialTrustTest remoteTest = newDomain.CreateInstanceAndUnwrap(
typeof(PartialTrustTest).FullName) as PartialTrustTest;
This will get slightly more complicated if you would have 2 assemblies, where one needs to be run in FullTrust and one in PartialTrust. The way to do this is to sign with a key (obtained by caling sn –k) the FullTrust assembly, and pass it’s strong name as the last parameter to AppDomain.CreateDomain.
//We consider FullTrustAssm to be the name of the assembly that needs to be
//executed as a full-trust assembly
AppDomain newDomain = AppDomain.CreateDomain(“test domain”, null, setup, ps,
2. Sandboxing AppDomains might seem complicated and daunting, especially if all you need is to launch an entire executable in PartialTrust and you don’t want to know all the details of AppDomain creation and communication.
If this is the case, we recommend the PTRunner tool. If you are going to take this route, you still need to remove the Deny, assembly level requests or the caspol command. In order to be able to use this tool, your application has to be launched from the command prompt. The tool is present in the CLR Security codeplex site at project PTRunner.
So let’s say your test is located in one assembly called “partialTrustAssembly.exe” that used to have Deny. You remove the deny and call PTRunner. Under the covers the PTRunner tool sets a sandbox AppDomain for you and launches your application in it.
Now let’s say, you want one of the assemblies your executable references to be full trust. You would do something like this:
PTRunner –af fullTrustAssembly.dll partialTrustAssembly.exe
You could also do something like this.
PTRunner –af FullTrustAssembly.exe FullTrustAssembly.exe .
This is rather fancy but it runs your assembly as full trust in a partial trust AppDomain. This would allow you to do operations allowed only to full-trust code, like assert, but at the same time run in a partiual-trust AppDomain so demands would still fail.
Or perhaps you don’t like the minimum execution permission that your assembly is run under. You could do something like this:
PTRunner –ps Internet partialTrustAssembly.exe
Or perhaps you want your very own permission set that is non-standard. You would write the permission in XML and call PTRunner like this:
PTRunner –xml Permission.xml partialTrustAssembly.exe
The xml file contains the serialization to XML of a permission set.
3. If you don’t have the luxury to launch a runner that, in turn, launches your test, another way to launch code in partial-trust is to use the SandboxActivator. This is located in the CLR Security codeplex site in the Security 1.1 library and will help you set up a new AppDomain in an easier way. This is basically a wrap around ApDomain.CreateDomain and AppDomain. CreateInstanceAndUnwrap from the example above about sandboxed domains and it returns you just an instance of your type in the new AppDomain.
You still need to remove the Deny, assembly level requests or the caspol command from your application and make one of the existing classes derive from MarshalByRefObject. Then you call the SandboxActivator.GetPartialTrustInstance which will return a new instance of your type in a partial-trust AppDomain. Calling a method on this new instance will be executed in a partial-trust sandbox.
//This will create a sandboxed AppDomain with the Execution permission set
MainClass m = SandboxActivator.GetPartialTrustInstance<MainClass>();