The DynamicMethod class is in a part of the .NET Framework that not many people touch, even less so in partial trust. You may ask, then, why I bother to cover it. I have two reasons: it is a lower-level abstraction on which LINQ expression compilation is built (and therefore a building block for a future post), and enabling light-weight code generation in partial trust can be somewhat tricky.
DynamicMethods, as their name implies, are methods created at runtime that are associated with an existing module or type, or a transparent assembly provided by the framework (aka an anonymously hosted DynamicMethod). There are different considerations for each, so we’ll tackle them one at a time.
Existing Module or Type
DynamicMethods can be associated with existing .NET modules and types by using the appropriate constructor overload of DynamicMethod that accepts either a Module or a Type as an owner. Using one of these constructors allows you to create a method that is logically associated with that owner, which means it has access to any non-public members within that same scope. The MSDN documentation has a great example of how to use this functionality.
Unfortunately, this example fails in medium trust because of one of the many security checks that DynamicMethods do in the case when there is an existing owner:
- If the DynamicMethod is associated with a Type, then if it is invoked from a Type that does not match the owner, then ReflectionPermission/MemberAccess is demanded.
- If the DynamicMethod is associated with a Module, then if it invoked from an Assembly that does not match the owner Module’s Assembly, then ReflectionPermission/MemberAccess is demanded.
- If the skipVisibility constructor parameter is set to true, then ReflectionPermission/MemberAccess is demanded.
These restrictions make associating DynamicMethods with existing modules or types almost impossible in partial trust. But if you find that you still want to do this, you should find out about additional restrictions from Shawn’s blog on the topic. (For example, what stops me from associating methods with modules and types from .NET Framework assemblies?
Anonymously Hosted
The solution to the problems above is to place your DynamicMethods in an anonymously hosted security-transparent assembly provided by the .NET Framework. Doing this simply requires you not to specify an owner Module or Type in the constructor for the DynamicMethod.
This narrows down your constructor choice from eight to two; the only difference between them is a very interesting parameter called restrictedSkipVisibility.
When this parameter is set to false, the JIT compiler treats the DynamicMethod like any other method in your code; that is, it can access all public members in other assemblies. If the parameter is true, that means the DynamicMethod can access non-public members in other assemblies without using reflection. This feature is subject to the restriction that the accessed assemblies must have a trust level that is equal to or less than the trust level of the call stack that emits the dynamic method. This check is done only at JIT compilation time and not during subsequent invocations of the method.
If you’re familiar with ReflectionPermission/RestrictedMemberAccess this pattern of demand probably sounds familiar to you. In fact, the mechanism is largely the same with the interesting difference that the demand for the appropriate permission is done against the call stack that was present when the DynamicMethod was created. Let’s look at a couple of examples.
First, let’s look at the power of restrictedSkipVisibility. Below, I have declared an interface, ICalculator, with an internal implementation named PrivateCalculator.
[sourcecode language="csharp"]
public interface ICalculator
{
int Add(int left, int right);
}
internal class PrivateCalculator : ICalculator
{
public int Add(int left, int right)
{
Console.WriteLine("Inside PrivateCalculator.Add.");
return left + right;
}
}
[/sourcecode]
So far, so good. Now let’s create a dynamic method that manufactures ICalculator instances.
[sourcecode language="csharp"]
public class Program : MarshalByRefObject
{
static void Main(string[] args)
{
var ps = new PermissionSet(PermissionState.None);
ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution | SecurityPermissionFlag.Infrastructure));
ps.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
AppDomain d = AppDomain.CreateDomain("Sandbox", null, new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory }, ps);
var x = (Program)d.CreateInstanceAndUnwrap(typeof(Program).Assembly.FullName, typeof(Program).FullName);
x.PartialTrustMain();
}
public void PartialTrustMain()
{
var dm = new DynamicMethod("CreatePrivateCalculator", typeof(ICalculator), Type.EmptyTypes, restrictedSkipVisibility: true);
var ilGenerator = dm.GetILGenerator();
ilGenerator.Emit(OpCodes.Newobj, typeof(PrivateCalculator).GetConstructor(Type.EmptyTypes));
ilGenerator.Emit(OpCodes.Ret);
var createCalculator = (Func<ICalculator>)dm.CreateDelegate(typeof(Func<ICalculator>));
Console.WriteLine(createCalculator().Add(5, 7));
}
}
[/sourcecode]
I include Main for completeness, but the real code of interest is inside PartialTrustMain, where I create an anonymously hosted DynamicMethod with restrictedSkipVisibility set to true. The method’s body simply creates a new instance of PrivateCalculator and returns it. Notice I am simply using the IL necessary to call the C# equivalent of "new PrivateCalculator()," and this compiles even though the method will not live in the same assembly as the PrivateCalculator class. If I removed the restrictedSkipVisibility parameter or set it to false, I would receive the following exception:
Unhandled Exception: System.MethodAccessException: Attempt by method ‘DynamicClass.CreatePrivateCalculator()’ to access method ‘CustomDynamicMethodSecurity.PrivateCalculator..ctor()’ failed.
at CreatePrivateCalculator()at System.Func`1.Invoke()
at CustomDynamicMethodSecurity.Program.PartialTrustMain()
at CustomDynamicMethodSecurity.Program.PartialTrustMain()
at CustomDynamicMethodSecurity.Program.Main(String[] args)
If we wanted to achieve the same thing without this parameter, we’d have to write the IL to generate calls against Activator.CreateInstance for the PrivateCalculator type, a verbose and error-prone set of lines to write by hand.
For the second example, let’s change the stakes a little bit. Let’s move ICalculator and PrivateCalculator to a separate assembly that is now fully trusted in the partial trust AppDomain. If we still try to create a PrivateCalculator using the DynamicMethod above, then we’ll encounter the same MethodAccessException that I pointed out earlier. Because PrivateCalculator is now in a fully trusted assembly, it requires full trust in order to create instances of PrivateCalculator from an anonymously hosted DynamicMethod. What else could we do?
Well it turns out we can move the creation of the DynamicMethod to a fully trusted assembly and then pass the delegate returned from DynamicMethod.CreateDelegate back to the console application to invoke. Let’s take a break here and look at the changes made to the code.
This is the console application:
[sourcecode language="csharp"]
public class Program : MarshalByRefObject
{
static void Main(string[] args)
{
var ps = new PermissionSet(PermissionState.None);
ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution | SecurityPermissionFlag.Infrastructure));
ps.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
AppDomain d = AppDomain.CreateDomain(
"Sandbox",
null,
new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory },
ps,
typeof(CalculatorUtils).Assembly.Evidence.GetHostEvidence<StrongName>());
var x = (Program)d.CreateInstanceAndUnwrap(typeof(Program).Assembly.FullName, typeof(Program).FullName);
x.PartialTrustMain();
}
public void PartialTrustMain()
{
var createCalculator = CalculatorUtils.CreatePrivateCalculatorFactory();
Console.WriteLine(createCalculator().Add(5, 7));
}
}
[/sourcecode]
And this is the fully trusted assembly:
[sourcecode language="csharp"]
public static class CalculatorUtils
{
public static Func<ICalculator> CreatePrivateCalculatorFactory()
{
var dm = new DynamicMethod("CreatePrivateCalculator", typeof(ICalculator), Type.EmptyTypes, restrictedSkipVisibility: true);
var ilGenerator = dm.GetILGenerator();
ilGenerator.Emit(OpCodes.Newobj, typeof(PrivateCalculator).GetConstructor(Type.EmptyTypes));
ilGenerator.Emit(OpCodes.Ret);
return (Func<ICalculator>)dm.CreateDelegate(typeof(Func<ICalculator>));
}
}
public interface ICalculator
{
int Add(int left, int right);
}
internal class PrivateCalculator : ICalculator
{
public int Add(int left, int right)
{
Console.WriteLine("Inside PrivateCalculator.Add.");
return left + right;
}
}
[/sourcecode]
But even with this code we have a problem. The call stack present when the DynamicMethod was created has some partially trusted code in it from the console application. So the last thing we need to do is apply an assert to CalculatorUtils.CreatePrivateCalculatorFactory that stops the stack walk from going into that partially trusted code. The final outcome:
[sourcecode language="csharp" padlinenumbers="true"]
public static class CalculatorUtils
{
[SecuritySafeCritical]
[ReflectionPermission(SecurityAction.Assert, MemberAccess = true)]
public static Func<ICalculator> CreatePrivateCalculatorFactory()
{
var dm = new DynamicMethod("CreatePrivateCalculator", typeof(ICalculator), Type.EmptyTypes, restrictedSkipVisibility: true);
var ilGenerator = dm.GetILGenerator();
ilGenerator.Emit(OpCodes.Newobj, typeof(PrivateCalculator).GetConstructor(Type.EmptyTypes));
ilGenerator.Emit(OpCodes.Ret);
return (Func<ICalculator>)dm.CreateDelegate(typeof(Func<ICalculator>));
}
}
[/sourcecode]
Now I should hope that it goes without saying but it’s very dangerous to pass around delegates created under an assert like this among class library boundaries. Treat them as radioactive if you must do this, and be sure to review your code for any possible exploitations where code might be able to invoke one of these delegates even though they should not be able to.
Next time, we’ll talk about expression compilation!