Search code examples
c#cilcode-access-securityilgenerator

Skip visibility checks for dynamically generated methods without DynamicMethod


This question is very similar to a two other questions see: first, second. However those are pretty out dated to say the least and I hope things have changed with .Net 5.

Now first up let me clarify the issue. With a simple example that tries to get the underlying array of a List<int>.

var method = new DynamicMethod("GetUnderlyingArray", typeof(int[]), new[] { typeof(List<int>) }, typeof(List<int>), true);

var ilGenerator = method.GetILGenerator();

ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, typeof(List<int>).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance));
ilGenerator.Emit(OpCodes.Ret);

var arrayGetter = (Func<List<int>, int[]>)method.CreateDelegate(typeof(Func<List<int>, int[]>));

This works perfectly fine as I am able to tell the DynamicMethod to skip the visibility checks (even though it works as well, when the last parameter of the DynamicMethod constructor true is removed).

However, when I am trying to do the same with the example down below, it will throw a FieldAccessException.

var assemblyName = new AssemblyName("Example");
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = assemblyBuilder.DefineDynamicModule(assemblyName.Name + ".dll");

var type = dynamicModule.DefineType("GetUnderlyingArrayClass");

var method = type.DefineMethod("GetUnderlyingArray", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, typeof(int[]), new[] { typeof(List<int>) });

var ilGenerator = method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, typeof(List<int>).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance));
ilGenerator.Emit(OpCodes.Ret);

type.CreateType();

var arrayGetter = (Func<List<int>, int[]>)type.GetMethod("GetUnderlyingArray").CreateDelegate(typeof(Func<List<int>, int[]>));

System.FieldAccessException: Attempt by method 'GetUnderlyingArrayClass.GetUnderlyingArray(System.Collections.Generic.List'1)' to access field 'System.Collections.Generic.List'1<System.Int32>._items' failed. + GetUnderlyingArrayClass.GetUnderlyingArray(List)

Here is a .Net fiddle link with the code shown above.

Now, one of the questions I mentioned points to the following attribute ReflectionPermissionAttribute. However as it states on the documentation Code Access Security is not supported or honored by the runtime.. From what I understand, this basically means that .Net Core/.Net 5 do not support CAS.

Here is where I am getting confused. Setting the skipVisibility parameter to true or false doesn't actually matter. I'd assume that is due to the fact that I am running the code in a .Net 5 environment. However, if CAS is not supported on .Net 5, why am I still able to read out the private field?

The goal is obviously to access a private field/method from a dynamically generated method using the DefineType/DefineMethod API's.


Solution

  • I am still not able to explain why setting the skipVisibility parameter to true/false doesn't matter, however PathogenDavid over on the GitHub from the csharp runtime discussion repository showed me a solution which was way easier than expected.

    // Add IgnoresAccessChecksTo attribute
    var ignoresAccessChecksTo = new CustomAttributeBuilder
    (
        typeof(IgnoresAccessChecksToAttribute).GetConstructor(new Type[] { typeof(string) }),
        new object[] { typeof(List<>).Assembly.GetName().Name }
    );
    
    assemblyBuilder.SetCustomAttribute(ignoresAccessChecksTo);
    

    See this .Net Fiddle for a working example.

    As he said the IgnoresAccessChecksTo attribute is used to bypass the checks.

    The runtime understands the attribute, but it is not defined in the BCL so you have to define it yourself.