• [C#] 解决Silverlight反射安全关键(SecuritySafeCritical)时报“System.MethodAccessException: 安全透明方法 XXX 无法使用反射访问”的问题


    作者: zyl910

    一、缘由

    在Silverlight中使用反射动态访问时,经常遇到“System.MethodAccessException: 安全透明方法 XXX 无法使用反射访问……”等错误。
    其中最常见的情况,是因为这些成员具有 安全关键(SecuritySafeCritical)的特性(SecuritySafeCriticalAttribute)。但是这个现象是不对劲的——Silverlight里编写的代码都是透明代码(SecurityTransparent),按照规则不能调用 关键代码(SecurityCritical),但规则允许它调用安全关键代码(SecuritySafeCritical)。
    而且后来测试了硬编码来调用,发现此时(硬编码)是能够正常调用的。且在.NET framework等平台中,是能正常调用的。

    虽然硬编码能调用,但是很多时候是需要反射动态访问的。故需要想办法解决。

    二、以Assembly.FullName为例进行尝试

    2.1 硬编码

    例如 Assembly 的 FullName属性就行这种情况。在硬编码的情况下是能成功调用的,源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    Debug.WriteLine(string.Format("#FullName:	{0}", assembly.FullName));
    

    2.2 用Type.InvokeMember进行反射调用

    可是当使用反射来访问该属性时,便会遇到异常了。例如通过 Type.InvokeMember 来反射获取FullName属性值,源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    Object rt = typ.InvokeMember(membername, BindingFlags.GetProperty, null, obj, null);
    Debug.WriteLine(rt);
    

    详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
      位于 System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    可见是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。

    2.3 用 PropertyInfo.GetValue 进行反射调用

    既然Type.InvokeMember,那就再试试用 PropertyInfo.GetValue 进行反射调用吧。源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    rt = pi.GetValue(obj, null);
    Debug.WriteLine(rt);
    

    发现该办法也是报异常。详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.RuntimeAssembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
      位于 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。
    还发现 System.Reflection.Assembly.get_FullName 实际是派生类(RuntimeAssembly)里覆写的方法。

    2.4 用 MethodInfo.Invoke 进行反射调用

    最后还可尝试用 MethodInfo.Invoke 进行反射调用吧。源码如下。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    MethodInfo mi = pi.GetGetMethod();
    rt = mi.Invoke(obj, null);
    Debug.WriteLine(rt);
    

    发现该办法仍是报异常。详细的异常内容是——

    System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 无法使用反射访问 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
      位于 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
      位于 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
      位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean& ishad)
    

    它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全检查时,抛出异常的。

    三、反编译的分析

    这种不能反射是很奇怪的,于是决定反汇编看看代码。

    这些类型是在mscorlib.dll里定义的。可用ILSpy等工具进行反编译分析。

    // C:Program Files (x86)Reference AssembliesMicrosoftFrameworkSilverlightv5.0mscorlib.dll
    // mscorlib, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
    

    3.1 Assembly.FullName

    Assembly.FullName 反编译后的代码为 ——

    // System.Reflection.Assembly
    public virtual string FullName
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    

    发现一个疑点——它没有安全关键(SecuritySafeCritical)的特性,那么它应该是透明(SecurityTransparent)的啊。怎么会报MethodAccessException异常呢?

    3.2 RuntimeAssembly.FullName

    RuntimeAssembly.FullName 反编译后的代码为 ——

    // System.Reflection.RuntimeAssembly
    public override string FullName
    {
        [SecuritySafeCritical]
        get
        {
            if (this.m_fullname == null)
            {
                string value = null;
                RuntimeAssembly.GetFullName(this.GetNativeHandle(), JitHelpers.GetStringHandleOnStack(ref value));
                Interlocked.CompareExchange<string>(ref this.m_fullname, value, null);
            }
            return this.m_fullname;
        }
    }
    

    原来安全关键(SecuritySafeCritical)的特性是在这里出现的。

    3.3 System.RuntimeMethodHandle

    System.RuntimeMethodHandle 反编译后的代码为 ——

    // System.RuntimeMethodHandle
    [SecurityCritical]
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern void PerformSecurityCheck(object obj, RuntimeMethodHandleInternal method, RuntimeType parent, uint invocationFlags);
    
    [SecurityCritical]
    internal static void PerformSecurityCheck(object obj, IRuntimeMethodInfo method, RuntimeType parent, uint invocationFlags)
    {
        RuntimeMethodHandle.PerformSecurityCheck(obj, method.Value, parent, invocationFlags);
        GC.KeepAlive(method);
    }
    

    它调了CLR中的内部代码(MethodImplOptions.InternalCall),无法反编译。不能再跟踪下去了。

    四、动态生成代码

    所有的反射用法都试过了,均无法成功访问这些属性。此时没路了吗?
    不,此时还可尝试动态生成代码的办法。因为之前硬编码时能调通。
    C#有2种动态生成代码的办法,分别是 IL Emit 与 Expression Tree。考虑到代码的可读性与可维护性,一般用Expression Tree比较好。

    4.1 初试用Expression Tree 动态访问属性

    以下是一个辅助函数,利用Expression Tree技术,将属性访问操作封装为一个委托。

            /// <summary>
            /// 根据 PropertyInfo , 创建 Func 委托.
            /// </summary>
            /// <param name="pi">属性信息.</param>
            /// <returns>返回所创建的 Func 委托.</returns>
            public static Func<object, object> CreateGetFunction(PropertyInfo pi) {
                MethodInfo getMethod = pi.GetGetMethod();
                ParameterExpression target = Expression.Parameter(typeof(object), "target");
                UnaryExpression castedTarget = getMethod.IsStatic ? null : Expression.Convert(target, pi.DeclaringType);
                MemberExpression getProperty = Expression.Property(castedTarget, pi);
                UnaryExpression castPropertyValue = Expression.Convert(getProperty, typeof(object));
                return Expression.Lambda<Func<object, object>>(castPropertyValue, target).Compile();
            }
    

    随后便可使用该函数,来动态调用属性。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = obj.GetType();
    PropertyInfo pi = typ.GetProperty(membername);
    Func< object, object> f = CreateGetFunction(pi);
    rt = f(obj);
    Debug.WriteLine(rt);
    

    可是,该办法还是遇到了异常。

    System.TypeAccessException: 方法“DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Object)”访问类型“System.Reflection.RuntimeAssembly”的尝试失败。
       位于 lambda_method(Closure, Object)
       位于 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
    

    4.2 解决Expression Tree法的问题

    难道真的没办法了吗?
    这时突然想起 RuntimeAssembly 是一个内部类(internal),而内部类应该是不能在程序集外访问的。
    于是修改了访问代码,通过基类 Assembly 来访问 FullName。

    Assembly assembly = typeof(Environment).Assembly;
    String membername = "FullName";
    Object obj = assembly;
    Type typ = typeof(Assembly);    // 强制指定基类。不能用 `obj.GetType()` ,因它返回的是 RuntimeAssembly 这个内部类。 
    PropertyInfo pi = typ.GetProperty(membername);
    Func< object, object> f = CreateGetFunction(pi);
    rt = f(obj);
    Debug.WriteLine(rt);
    

    此时终于能正常的读出FullName属性的值了。

    随后尝试反射时也强制指定基类(Assembly),但仍是报异常。可能是它内部限制了。
    而且还尝试了用Expression Tree访问关键代码(SecurityCritical)的内容,希望能突破安全限制。可是还是遇到了MethodAccessException异常,看来安全性很严格,没有漏洞。

    五、经验总结

    • 在 Silverlight中,安全关键(SecuritySafeCritical)的成员无法反射。而且当实际类中有安全关键(SecuritySafeCritical)特性时(如RuntimeAssembly),即使是通过安全透明的基类(如Assembly)来反射,也是不行的。
    • 当无法通过反射动态获取属性时,可考虑动态生成代码的方案(IL Emit、Expression Tree)。
    • 对于内部类(internal),是无法突破安全限制来访问的,即使是反射、动态生成代码也不行。此时可考虑通过基类来访问。
    • 对于关键代码(SecurityCritical),是无法突破安全限制来访问的,即使是反射、动态生成代码也不行。

    源码地址:

    https://github.com/zyl910/vscsprojects/tree/master/vs14_2015/Silverlight/TestSilverlight

    参考文献

  • 相关阅读:
    sql server登录账户看不到sql server代理和维护计划
    Redis(1.19)redis内存消耗、redis内存优化
    【最佳实践】bat实现自动化运行sql
    Redis(1.18)redis阻塞分析
    某机场网络环境分析
    【js】setInterval是个坑!chrome定时截图保存实现
    【操作系统】CPU中的时间片的概念
    Jeff Atwood:软件工程已死?
    vscode配置 eslint+prettierrc自动格式化vue3、ts、tsx文件
    基于.NET的大型Web站点StackOverflow架构分析
  • 原文地址:https://www.cnblogs.com/zyl910/p/cs_silverlight_reflect_safecritical.html
Copyright © 2020-2023  润新知