[This article is still a WIP. Updates may follow.]
Recently I am working on a C# sandbox for C# code, and come across a lot of buggy situations. Here I am just trying to sort them out.
To begin with…
I want to setup up a sandbox that executes C#. By mentioning “sandbox” I mean the hosted C# code must not “break the fourth wall”. For example, it shouldn’t have access to files (other than assembly files) and unmanaged code.
To begin with, I headed to porting.md and found out the following statements
Code Access Security (CAS)
Justification. Sandboxing, i.e. relying on the runtime or the framework to constrain which resources a managed application or library can run is not supported on .NET Framework and therefore is also not supported on .NET Core or .NET Native. We believe that there are simply too many pieces in the .NET Framework and runtime that can result in elevation of privileges. Thus we don’t treat CAS as security boundary anymore. On top of that, it makes the implementation more complicated and often has correctness performance implications for applications that don’t intend to use it.
Replacement. Use operating system provided security boundaries, such as virtualization, containers, or user accounts for running processes with the least set of privileges.
If you click on the “not supported on .NET Framework” link, you will also see a big “Caution” box telling you
We are updating our guidance to reflect that Code Access Security and Security-Transparent Code will not be supported as a security boundary with partially trusted code, especially code of unknown origin.
I am not going to elaborate how in-viable it is to “use virtualization, containers, or user accounts for running processes with the least set of privileges”. I just want do get my work done. So, CAS, I choose you. No possibility for .NET Core. Pity. I want to mention they have already re-introduced AppDomain
& BinaryFormatter
in .NET Standard/Core 2.0, though current AppDomain
API is still a little bit weak. I also want to mention that I am actually a huge fan of .NET Core and .NET Standard.
So, .NET Framework 4.6.2 and .NET Standard 2.0; that’s my choice.
Starting with AppDomain
I have seldom used AppDomain
API, but now I get a chance. Using documentation as reference, I made out an AppDomain
with least permissions ever, and used my JSON-RPC library for sandboxed AppDomain to directly talk with sandbox clients. Basically, you need to use the following overload to set up a partially trusted execution environment
AppDomain.CreateDomain( string friendlyName, Evidence securityInfo, AppDomainSetup info, PermissionSet grantSet, params StrongName[] fullTrustAssemblies);
While it’s easy to just run some overly simple demo code in the sandbox, it is much difficult to run some real-world code with some dependencies, namely, Newtonsoft.Json and JsonRpc.Standard; the latter is my JSON-RPC library. It is just difficult to get it right. For example, I found out that, if I used JsonRpcException
of JsonRpc.Standard in the sandboxed AppDomain
, CLR will inevitably throw a TypeLoadException
:
System.TypeLoadException:“Inheritance security rules violated while overriding member: 'JsonRpc.Standard.Client.JsonRpcRemoteException.GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Security accessibility of the overriding method must match the security accessibility of the method being overriden.”
Okay… CAS stuff. So I marked my GetObjectData
with [SecurityCritical]
, just as the one in System.Exception
. However, the problem is still there. I checked up my code over and over again. Surely it is not caused by the conditional switch?
I googled StackOverflow, and found out I am not alone. However, none of the answers solved my problem.
So… Is my GetObjectData
implementation really security-critical? Maybe somehow the method is actually not security-critical at run time. Time for a test. I created the following class, and loaded the assembly into the sandboxed AppDomain.
public class MyClass { [SecurityCritical] public void Method() { } }
Then, at run time, evaluate typeof(SecurityCriticalLibrary.MyClass).GetMethod(“Method”) in the Watch window, and I have
Guess what? It’s security-transparent. What the…