More Implicit Uses of CAS Policy: loadFromRemoteSources

In my last post about changes to the CLR v4 security policy model, I looked at APIs which implicitly use CAS policy in their operation (such as Assembly.Load overloads that take an Evidence parameter), and how to migrate code that was using those APIs.   There are another set of assembly loads which cause implicit use of CAS policy, which I’ll look at today – these are loads from remote sources.

For example, in .NET 3.5 the following code:

 Assembly internetAssembly = Assembly.LoadFrom(@"https://www.microsoft.com/assembly.dll");Assembly intranetAssembly = Assembly.LoadFrom(@"\\server\share\assembly.dll");

Will by default load internetAssembly with the Internet permission set and intranetAssembly with the LocalIntranet permission set.   That was because the CLR would internally gather evidence for both assemblies and run that evidence though CAS policy in order to find the permission set to grant that assembly.

Now that the sandboxing model has changed in the v4 CLR, there is no more CAS policy to apply the assembly’s evidence to by default, and  therefore default behavior of both of these loads would be to load the assemblies with a grant set of full trust.

That creates a problem for code which was written before .NET 4 shipped – this code may quite reasonably be expecting that the above assembly loads are safe because the CLR will automatically apply a restricted grant set to the assemblies if they are coming from a remote location.   Now when the code runs in the v4 CLR, the assemblies are elevated to full trust, which amounts to a silent elevation of privilege bug against the .NET 2.0 code which was expecting that these assemblies be sandboxed.  Obviously that’s not a good thing.

Instead of silently granting these assemblies full trust, the v4 CLR will actually take the opposite approach.  We’ll detect that these assemblies are being loaded in such a way that

  1. They would have been sandboxed by the v2 CLR and
  2. Are going to be given full trust by the v4 CLR

Once we detect an assembly load where both of the above conditions are true, the CLR will refuse to load the assembly with the following message:

System.IO.FileLoadException: Could not load file or assembly ' <assemblyPath> ' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515 (COR_E_NOTSUPPORTED)) --->

System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See https://go.microsoft.com/fwlink/?LinkId=131738 for more information.

This exception is saying “The v4 CLR is not going to sandbox the assembly that you’re trying to load, however the v2 CLR would have.  We don’t know if that’s safe in your application or not, so we’re going to fail the assembly load to ensure that your application is secure by default.  However, if this is a safe assembly load, go ahead and enable loading from remote sources for this process.”

That leads to the next question -- how do you know if it is safe to enable loadFromRemoteSources in your application?  This decision generally comes down to applying these tests:

  1. Do you trust the string that you’re passing to Assembly.LoadFrom?
  2. Do you trust the assembly that you’re loading?
  3. Do you trust the server hosting the assembly (and the network path from the server back to your application)?

If you answered yes to all three questions then your application is a good candidate for enabling the loadFromRemoteSources switch.  If you answered no to any of the three questions, then you may need to take further action before enabling the switch and loading the assembly.   (For instance, you may have some application logic to ensure that the string being passed to LoadFrom is going to a server you trust, or your application might download the assembly first and verify it has an Authenticode signature that it trusts).

Let’s look at some examples:

The most straight-forward reason that you would want to enable this is in the case that you know what the assemblies you are loading are, you trust them, and you trust the server that they are hosted on.  For example, if your application is hosted on a share on your company’s intranet, and happens to need to load other assemblies from other shares on the network, you probably want to enable the switch.   (In many cases, this category of applications used to have to fight with CAS policy to get things loaded the way they wanted, now with loadFromRemoteSources set things should just work.)

On the other hand, if you are an application that takes as untrusted input a string which then is passed through to Assembly.LoadFrom, you probably don’t want to enable this switch, as you might be opening yourself up to an elevation of privilege attack via that untrusted input.

Similarly, if your application takes as input an assembly name to LoadFrom, however you trust that input.  (Maybe it comes directly from your application’s user, and there is no trust boundary between the user and your app – for instance, the user is pointing you at a plugin they trust and wish to load in the app), you may also want to enable this switch.

Another consideration to take into account when considering loadFromRemoteSources is that this is a process-wide configuration switch.  This means that it applies to all places in your code which loads assemblies, not just a single LoadFrom call.  If you only trust the inputs to some of your assembly loads, then you may wish to consider not using the loadFromRemoteSources switch and instead take a different approach.

Since the first condition for the NotSupportedException that blocks remote assembly loads is that the load would have been sandboxed by the v2 CLR, one alternate way to enable these loads without setting loadFromRemoteSources for the entire process is to load the assemblies into a domain that you create with the simple sandboxing API.

This will work because even in v2.0, simple sandbox domains never apply CAS policy, and therefore any remote loads in simple sandbox domains would not have required CAS policy to sandbox them.  Since the assemblies would not have used CAS policy in v2, the loads are considered safe to use in v4 as well, and will succeed without the NotSupportedException being thrown.

For example, if you want to enable only a subset of LoadFroms to load assemblies in full trust, if you create a fully trusted simple sandbox, then any assemblies loaded into that sandbox would have the same full trust grant set in v2 as in v4.   (The full trust grant set of the domain applies to all assemblies loaded into it).   This will cause the CLR to allow the loads to proceed in full trust in v4 without having to throw the switch.

 // Since this application only trusts a handful of LoadFrom operations,// we'll put them all into the same AppDomain which is a simple sandbox// with a full trust grant set.  The application itself will not enable// loadFromRemoteSources, but instead channel all of the trusted loads// into this domain.PermissionSet trustedLoadFromRemoteSourceGrantSet    = new PermissionSet(PermissionState.Unrestricted); AppDomainSetup trustedLoadFromRemoteSourcesSetup = new AppDomainSetup();trustedLoadFromRemoteSourcesSetup.ApplicationBase =    AppDomain.CurrentDomain.SetupInformation.ApplicationBase; AppDomain trustedRemoteLoadDomain =    AppDomain.CreateDomain("Trusted LoadFromRemoteSources Domain",                           null,                           trustedLoadFromRemoteSourcesSetup,                           trustedLoadFromRemoteSourcesGrantSet); // Now all trusted remote LoadFroms can be done in the trustedRemoteLoadDomain,// and communicated with via a MarshalByRefObject.

As an example in the opposite direction, maybe your application has mostly loads which are safe to have remote targets, however there are a small handful of places that do need to be sandboxed.  By creating a simple sandboxed AppDomain for those loads, you can then safely set the loadFromRemoteSources switch for the rest of your process.

 // Since this application trusts almost all of its assembly loads, it// is going to enable the process-wide loadFromRemoteSources switch.// However, the loads that it does not trust still need to be sandboxed. // First figure out a grant set that the CLR considers safe to apply// to code from the Internet.Evidence sandboxEvidence = new Evidence();sandboxEvidence.AddHostEvidence(new Zone(SecurityZone.Internet));PermissionSet remoteLoadGrantSet = SecurityManager.GetStandardSandbox(sandboxEvidence); AppDomainSetup remoteLoadSetup = new AppDomainSetup();trustedLoadFromRemoteSourcesSetup.ApplicationBase = GetSandboxRoot(); AppDomain remoteLoadSandbox =    AppDomain.CreateDomain("Remote Load Sandbox",                           sandboxEvidence,                           remoteLoadSetup,                           remoteLoadGrantSet); // Now all trusted remote LoadFroms can be done in the default domain// with loadFromRemoteSources set, and untrusted loads can be done// in the sandbox that we just setup.

(Similarly, if the process is in legacy CAS policy mode, the v4 CLR will have the same behavior as the v2 CLR, and there will be no exception).

Let’s say that you’ve considered the security implications and your application is a good candidate to enable loadFromRemoteSources, how do you go about doing so?   Basically, you just provide a .exe.config file for your application with a loadFromRemoteSources runtime switch enabled.   So, if your application’s entry point is YourApp.exe, you’ll want to make a YourApp.exe.config.   (Or use the app.config file in your Visual Studio project).   This configuration file will need to contain runtime section such as:

 <configuration>  <runtime>    <loadFromRemoteSources enabled="true" />  </runtime></configuration>

This setting will cause the CLR to notice that even though it is going to load an assembly that would have been sandboxed in the v2 runtime, your application has explicitly stated that this is a safe thing to do.   Since your application has said that it understands the security impact of loading from remote locations and it is safe in the context of this application, the CLR will then allow these loads to succeed without throwing a NotSupportedException to block them.