开发者

Granting reflection permission to a dynamically created assembly

开发者 https://www.devze.com 2023-02-18 17:14 出处:网络
I am writing a simple desktop client/server application in C#.For self-educational purposes, I built my own serialization system for the messages (defined as classes) sent back and forth between the t

I am writing a simple desktop client/server application in C#. For self-educational purposes, I built my own serialization system for the messages (defined as classes) sent back and forth between the two applications over a tcp/ip socket connection. The system uses reflection at initialization time to construct serialize/deserialize methods for each message type by emitting IL.

The first version of this system used DynamicMethod, passing in true to the the constructor to allow the generated IL (that's operating on arbitrary fields in the message type) to ignore access permissions. This worked and the people rejoiced, but I was dissatisfied with how painfully opaque debugging the resulting functions was. So I decided to ditch DynamicMethod and use the *Builder classes to construct a dynamic assembly which I could optionally save to disk and examine with a tool like .NET Reflector.

I refactored the system and then hit a bit of a brick wall. Any time one of the new serialization functions attempts to access a private field or method in one of my message types I get a FieldAccessException or MethodAccessException. After much googling and gnashing of teeth, I think I have narrowed the problem to one of permissions; in particular I think my dynamically created assembly is missing the ReflectionPermissionFlag.MemberAccess permission relative to the calling/constructing assembly (where all the reflected types are sitting).

Unfortunately, I can't seem to figure out how to modify the dynamic assembly creation process such that the assembly has reflection permission back into the creating assembly. The permissions parameters to DefineDynamicAssembly seem related to restricting permission, not granting it, which leaves us with the Evidence parameter. Evidence seems to magically translate into a set of permissions, but I can't find any useful examples or explanations for how this occurs.

So my questions are:

(1) Am I correct in my assumption that my problem is a lack of permission on my dynamically created assembly?

(2) If so, how do I, as the calling assembly, grant the necessary permission to my dynamic assembly?

The current dynamic assembly creation code:

        AssemblyName assembly_name = new AssemblyName( "LCSerialization" );
        assembly_name.Version = new Version( 1, 0, 0, 0 );

        m_SerializationAssembly = current_domain.DefineDynamicAssembly( assembly_name, AssemblyBuilderAccess.RunAndSave );  // Fix me
        m_SerializationModule = m_SerializationAssembly.DefineDynamicModule( "MainModule", "LCSerialization.dll" );
        m_SerializationWrapperClass = m_SerializationModule.DefineType( "CSerializationWrapper", TypeAttributes.Public );

Note that my project is targeting .NET 3.5; the documentation claims .NET 4.0 uses a different notion of security and deprecates the Evidence/PemissionSet-based methods in DefineDynamicAssembly.

To give a concrete example, suppose I had a class like:

[NetworkMessage]
public class CTestMessage
{
    public CTestMessage( int cheeseburgers )
    {
        m_CheeseBurgers = cheeseburgers
    }

    private int m_CheeseBurgers = 0;
}

then my serialization system, upon encountering this during init reflection, would roughly end up doing (cut-and-paste isnt possible here) the following with type = typeof( CTestMessage ):

MethodBuilder serialization_builder = m_SerializationWrapperClass.DefineMethod( "Serialize_" + type.Name, 
                                                                                MethodAttributes.Public | MethodAttributes.Static, 
                                                                                null,
                                                                                new [] { type, typeof( BinaryWriter ) } );

ILGenerat开发者_如何学JAVAor s_il_gen = serialization_builder.GetILGenerator();
BindingFlags binding_flags_local_non_static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

s_il_gen.Emit( OpCodes.Ldarg_1 ); // Eval Stack: BinaryWriter
s_il_gen.Emit( OpCodes.Ldarg_0 ); // Eval Stack: BinaryWriter, testmessage
s_il_gen.Emit( OpCodes.Ldfld, type.GetField( "m_CheeseBurgers", binding_flags_local_non_static ) ); // Eval Stack: BinaryWriter, int
s_il_gen.Emit( OpCodes.Callvirt, typeof( BinaryWriter ).GetMethod( "Write", new Type[] { typeof( Int32 ) } ) ); // Eval Stack:
s_il_gen.Emit( OpCodes.Ret );

When the method is subsequently executed, the exception is thrown on the Ldfld instruction.

Edit: More detail demonstrating that what I'm asking for ought to be possible. Take the above code snippet, but replace MethodBuilder with DynamicMethod:

DynamicMethod serialization_builder = new DynamicMethod( "Serialize_" + type.Name, null, new [] { type, typeof( BinaryWriter ) }, true );

Now create a delegate from the DynamicMethod:

delegate void TestDelegate( CTestMessage, BinaryWriter );

TestDelegate test_delegate = serialization_builder.CreateDelegate( typeof( TestDelegate ) );

This delegate gets JITed and executes properly with no errors:

CTestMessage test_message = new CTestMessage( 5 );
BinaryWriter writer = new BinaryWriter( some_stream );
test_delegate( test_message, writer );


The problem is that the field is private. If you make it public the external method works fine. The DynamicMethod works despite it being private because the CLR apparently allows intra-module private field access - from the SSCLI, clsload.cpp@2659:

// pCurrentClass can be NULL in the case of a global function
// pCurrentClass it the point from which we're trying to access something
// pTargetClass is the class containing the member we are trying to access
// dwMemberAccess is the member access within pTargetClass of the member we are trying to access
BOOL ClassLoader::CheckAccess(EEClass *pCurrentClass,
                              Assembly *pCurrentAssembly,
                              EEClass *pTargetClass,
                              Assembly *pTargetAssembly,
                              DWORD dwMemberAccess)
{
    // we're trying to access a member that is contained in the class pTargetClass, so need to 
    // check if have access to pTargetClass itself from the current point before worry about 
    // having access to the member within the class
    if (! CanAccessClass(pCurrentClass,
                         pCurrentAssembly, 
                         pTargetClass, 
                         pTargetAssembly))
        return FALSE;

    if (IsMdPublic(dwMemberAccess))
        return TRUE;

    // This is module-scope checking, to support C++ file & function statics.
    if (IsMdPrivateScope(dwMemberAccess)) {
        if (pCurrentClass == NULL)
            return FALSE;

        _ASSERTE(pTargetClass);

        return (pCurrentClass->GetModule() == pTargetClass->GetModule());
    }

To access private fields externally you probably have to use reflection which pretty much defeats the purpose.

Edit Just to clarify, what you posted uses reflection to create the assembly, but the IL you generate doesn't use reflection to access the field - that's a plain old direct field access, which explodes because the target field is external and private. You'd have to emit IL which itself uses Type.GetField().GetValue() which is pretty pointless.


Yes, dynamic assemblies don't permit such access, whether in .NET 3.5 or 4+. I had the same problem. My workaround is to break out the actual IL-emitting code into a function taking an ILGenerator and to call it twice with other arguments the same, once (optionally) with an ILGenerator from a method in a dynamic assembly I'd save to disk to peverify/ildasm/etc. and once with an ILGenerator from a DynamicMethod. This way identical IL is emitted into both methods.

0

精彩评论

暂无评论...
验证码 换一张
取 消