A first hand look from the .NET engineering teams
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>();
These are the public APIs available through the SandboxActivator interface:
public static AppDomain CreateSandboxedDomain(PermissionSet ps, params Assembly fullTrustList)
public static T GetPartialTrustInstance<T>(PermissionSet ps, params Assembly fullTrustList) where T : MarshalByRefObject
public static T GetPartialTrustInstance<T>(params Assembly fullTrustList) where T : MarshalByRefObject
4. The last method to make your application work is not typically recommended by us. We have a way to set your process to run in legacy mode that is according to the pre-v4.0 security model. We prefer users of.Net Framework to fully migrate to the new security model; we introduced this only for exception situations. This legacy mode will be available only in v4 and in future versions would be removed. We also believe the new security model does a better job at setting up a sandbox for your application to run.
So there, you have been warned to not use this method. To follow this migration path, you keep the code as it is, and add a config file for your executable that will look like this:
<NetFx40_LegacySecurityPolicy enabled="true" />
This sounds like the right thing to do. After all, the CAS policy stuff was not only overcomplicated, but also underdocumented and buggy. (What a combination!)
I'm really looking forward to the new model, but there's one thing that I'd like you to clarify:
With the current model, it is possible to elevate an assembly to full trust, even if it is loaded in, say, IE. So although IE explicitly uses APIs to load its controls in a partial trust zone, we can give it full trust without IE even noticing.
With the other options, I believe that each and every application would have to manage exceptions to their partial trust policy, right? And if the application doesn't have configuration options to do so, there is no way to work around it, right?
While this was a real PITA, at least it was possible after some trial&error.
In case my assumptions are correct, do you have any idea how various app teams at MSFT are dealing with this? Are you working with them? (I'm particularly interested in IE)
What you call overrides at three places are actually overloads. I didn't bother reading rest of the article after those mistakes.
It is no longer possible for partially trusted code to be elevated to full trust the way it was in the previous model. You’re pretty much right on – each host needs to provide its own interface for trusting assemblies. It is very simple to administer exceptions to the partial trust permission set with the CreateDomain API that Cristian describes.
The CreateDomain API creates an AppDomain where there are two trust levels – full trust and partial trust. Everything loaded into the domain is partially trusted unless it’s on the full trust list – the ‘params StrongName fullTrustAssemblies’ argument in the overload.
This, coupled with the HostSecurityManager features provided (http://msdn.microsoft.com/en-us/library/system.security.hostsecuritymanager_members(VS.100).aspx ), provides the host the ability to define its policy for hosted code.
We are indeed working with the various teams that host partially trusted code internally. What particular IE scenario are you interested in?
MS Common Language Runtime Security Program Manager
thanks for your response. We have a managed IE control that allows an HTML-based app to interact with the desktop. We use CAS Policy to give this control (or rather, its assembly) full trust.
Wouldn't it be nice if the SandboxActivator could automatically read some standard configuration section from the from app.config file? This way, users would not depend on every single app to include some exception mechanism, and app authors wouldn't have to do the work?
(Then of course you'd have to make sure that every critical app, like IE, uses SandboxActivator, or provide some easy way to access that config from unmanaged code.)
The gist of the new CAS policy model is that everything unhosted runs as fully trusted, and hosts get to decide what the security policy is for their hosted code. Applications will have full trust unless they’re hosted, and there’s no way for the hosted, partial trust apps to elevate out of the host’s sandbox. To allow that would remove from the host’s complete control of its own security policy, which is one of the things we set out to provide for this release.
thanks for the clarification. I disagree with your conclusions, though.
I understand the reasons for the change and I agree with your decision, but I think it's a mistake to remove the option to opt out of the sandboxing model altogether. This leaves us with no option to use managed code for browser controls in situations where we need full control.
Sure, the app needs to have control, but your SandboxActivator could provide a nice standard way to let the user configure exceptions. (The app could still decide to disable that.)
Silverlight is superior in many situations, granted, but Silverlight code always runs in a sandbox. (This is an interesting topic on its own. Compare this to Moonlight, which can run code inside the sandbox, but also on top of the normal Mono runtime - what would be your Desktop CLR. That allows for code that can run in the browser, but the same code can run with full permissions. A nice option for enterprise apps that can run online or disconnected, with a local DB and everything, or need to interact with desktop apps like Office. And the compatibility story is obviously much smoother than Silverlight/WPF. But I digress.)
I wonder why the assemblies loaded from byte array by the MBR object in the sandboxed appdomain inherit the permission set from the loader assembly. I expected their permission set to match the appdomain’s. What's the technique for having partially trusted dlls loaded dynamically from byte?
Probably the old security logic with CAS was complex, and it took a while to understand, but once you figured it out, it was flexible, and could be configured in a lot of different ways.
You could grant specific permissions to specific assemblies by rolling out an MSI package. Once this was done within our company, it was no problem for us to create a signed ActiveX, which would run in the browser but still access local files.
Now with the new logic you have either full trust or the predefined set of permissions as decided by the hosting application. Only two possibilities, instead of millions of different combinations. And as there are no settings for this within the typical host IE - you are stuck with the default ones. For us this means, that in .NET for, we can not implement our ActiveX any longer.
Also, IMHO in an ideal world, assemblies should define what permission they would like to get, but the real permissions granted should be decided by system administrator. This is now definitely not possible any longer, as the CASpol tool is obsolete.
IMHO, you went in the wrong direction...
If it is complex maybe you can make it simpler or explain better in the documentation.
Removing it altogether?!!! makes things a lot more complicated. How is this simpler?
Just keep it as an option like turning the setting flag on. Why remove it????