如何在不违反继承安全规则的情况下在 .NET 4+ 中实现 ISerializable? [英] How can I implement ISerializable in .NET 4+ without violating inheritance security rules?

查看:22
本文介绍了如何在不违反继承安全规则的情况下在 .NET 4+ 中实现 ISerializable?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:Noda Time 包含很多可序列化的结构.虽然我不喜欢二进制序列化,但我们收到了许多支持它的请求,回到 1.x 时间线.我们通过实现 ISerializable 接口来支持它.

Background: Noda Time contains many serializable structs. While I dislike binary serialization, we received many requests to support it, back in the 1.x timeline. We support it by implementing the ISerializable interface.

我们最近收到了一个问题野田的报告时间 2.x 在 .NET 中失败小提琴.使用 Noda 的相同代码时间 1.x 工作正常.抛出的异常是这样的:

We've received a recent issue report of Noda Time 2.x failing within .NET Fiddle. The same code using Noda Time 1.x works fine. The exception thrown is this:

覆盖成员时违反了继承安全规则:'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)'.安全覆盖方法的可访问性必须与安全性相匹配被覆盖的方法的可访问性.

Inheritance security rules violated while overriding member: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.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.

我已将范围缩小到目标框架:1.x目标 .NET 3.5(客户端配置文件);2.x 面向 .NET 4.5.他们有在支持 PCL 与 .NET Core 以及项目文件结构,但看起来这是无关紧要的.

I've narrowed this down to the framework that's targeted: 1.x targets .NET 3.5 (client profile); 2.x targets .NET 4.5. They have big differences in terms of support PCL vs .NET Core and the project file structure, but it looks like this is irrelevant.

我已经成功地在本地项目中重现了这个,但我没有找到了解决办法.

I've managed to reproduce this in a local project, but I haven't found a solution to it.

在 VS2017 中重现的步骤:

Steps to reproduce in VS2017:

  • 创建一个新的解决方案
  • 创建面向 .NET 的新经典 Windows 控制台应用程序4.5.1.我称它为CodeRunner".
  • 在项目属性中,转到签名并使用以下命令对程序集进行签名一把新钥匙.取消密码要求,并使用任何密钥文件名.
  • 粘贴以下代码以替换 Program.cs.这是一这个微软样本.我保持所有路径相同,所以如果你想回到更完整的代码,您不需要更改任何其他内容.
  • Create a new solution
  • Create a new classic Windows console application targeting .NET 4.5.1. I called it "CodeRunner".
  • In the project properties, go to Signing and sign the assembly with a new key. Untick the password requirement, and use any key file name.
  • Paste the following code to replace Program.cs. This is an abbreviated version of the code in this Microsoft sample. I've kept all the paths the same, so if you want to go back to the fuller code, you shouldn't need to change anything else.

代码:

using System;
using System.Security;
using System.Security.Permissions;

class Sandboxer : MarshalByRefObject  
{  
    static void Main()  
    {  
        var adSetup = new AppDomainSetup();  
        adSetup.ApplicationBase = System.IO.Path.GetFullPath(@"..\..\..\UntrustedCode\bin\Debug");  
        var permSet = new PermissionSet(PermissionState.None);  
        permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));  
        var fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<System.Security.Policy.StrongName>();  
        var newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);  
        var handle = Activator.CreateInstanceFrom(  
            newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,  
            typeof(Sandboxer).FullName  
            );  
        Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();  
        newDomainInstance.ExecuteUntrustedCode("UntrustedCode", "UntrustedCode.UntrustedClass", "IsFibonacci", new object[] { 45 });  
    }  

    public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)  
    {  
        var target = System.Reflection.Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
        target.Invoke(null, parameters);
    }  
}

  • 创建另一个名为UntrustedCode"的项目.这应该是一个经典桌面类库项目.
  • 签署大会;您可以使用新密钥或与之前相同的密钥代码运行器.(这部分是为了模仿野田时间的情况,部分是为了让代码分析开心.)
  • 将以下代码粘贴到 Class1.cs 中(覆盖那里的内容):
    • Create another project called "UntrustedCode". This should be a Classic Desktop Class Library project.
    • Sign the assembly; you can use a new key or the same one as for CodeRunner. (This is partially to mimic the Noda Time situation, and partly to keep Code Analysis happy.)
    • Paste the following code in Class1.cs (overwriting what's there):
    • 代码:

      using System;
      using System.Runtime.Serialization;
      using System.Security;
      using System.Security.Permissions;
      
      // [assembly: AllowPartiallyTrustedCallers]
      
      namespace UntrustedCode
      {
          public class UntrustedClass
          {
              // Method named oddly (given the content) in order to allow MSDN
              // sample to run unchanged.
              public static bool IsFibonacci(int number)
              {
                  Console.WriteLine(new CustomStruct());
                  return true;
              }
          }
      
          [Serializable]
          public struct CustomStruct : ISerializable
          {
              private CustomStruct(SerializationInfo info, StreamingContext context) { }
      
              //[SecuritySafeCritical]
              //[SecurityCritical]
              //[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
              void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
              {
                  throw new NotImplementedException();
              }
          }
      }
      

      运行 CodeRunner 项目会出现以下异常(重新格式化以提高可读性):

      Running the CodeRunner project gives the following exception (reformatted for readability):

      未处理的异常:System.Reflection.TargetInvocationException:
      调用的目标已抛出异常.
      --->
      System.TypeLoadException:
      覆盖成员时违反了继承安全规则:
      'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData(...).
      覆盖方法的安全可访问性必须与安全性相匹配
      被覆盖的方法的可访问性.

      Unhandled Exception: System.Reflection.TargetInvocationException:
      Exception has been thrown by the target of an invocation.
      --->
      System.TypeLoadException:
      Inheritance security rules violated while overriding member:
      'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData(...).
      Security accessibility of the overriding method must match the security
      accessibility of the method being overriden.

      注释掉的属性显示了我尝试过的东西:

      The commented-out attributes show things I've tried:

      • SecurityPermission 由两篇不同的 MS 文章推荐(首先second),虽然有趣的是,他们围绕显式/隐式接口实现做了不同的事情
      • SecurityCritical 是 Noda Time 目前拥有的,也是 这个问题的答案 建议的内容
      • SecuritySafeCritical 代码分析规则消息在某种程度上建议了
      • 没有任何属性,代码分析规则很满意——有SecurityPermissionSecurityCritical目前,规则告诉您删除属性 - 除非您确实拥有 AllowPartiallyTrustedCallers.在任何一种情况下遵循建议都无济于事.
      • Noda Time 应用了 AllowPartiallyTrustedCallers;无论是否应用了属性,这里的示例都不起作用.
      • SecurityPermission is recommended by two different MS articles (first, second), although interestingly they do different things around explicit/implicit interface implementation
      • SecurityCritical is what Noda Time currently has, and is what this question's answer suggests
      • SecuritySafeCritical is somewhat suggested by Code Analysis rule messages
      • Without any attributes, Code Analysis rules are happy - with either SecurityPermission or SecurityCritical present, the rules tell you to remove the attributes - unless you do have AllowPartiallyTrustedCallers. Following the suggestions in either case doesn't help.
      • Noda Time has AllowPartiallyTrustedCallers applied to it; the example here doesn't work either with or without the attribute applied.

      如果我将 [assembly: SecurityRules(SecurityRuleSet.Level1)] 添加到 UntrustedCode 程序集(并取消注释 AllowPartiallyTrustedCallers 属性),但我认为这对于可能妨碍其他代码的问题来说是一个糟糕的解决方案.

      The code runs without an exception if I add [assembly: SecurityRules(SecurityRuleSet.Level1)] to the UntrustedCode assembly (and uncomment the AllowPartiallyTrustedCallers attribute), but I believe that's a poor solution to the problem that could hamper other code.

      我完全承认在这种情况下我很迷茫.NET 的安全方面.那么我如何可以针对 .NET 4.5 和但允许我的类型实现 ISerializable 并仍然用于.NET Fiddle 等环境?

      I fully admit to being pretty lost when it comes to this sort of security aspect of .NET. So what can I do to target .NET 4.5 and yet allow my types to implement ISerializable and still be used in environments such as .NET Fiddle?

      (虽然我的目标是 .NET 4.5,但我相信是 .NET 4.0 安全策略更改导致了问题,因此是标签.)

      (While I'm targeting .NET 4.5, I believe it's the .NET 4.0 security policy changes that caused the issue, hence the tag.)

      推荐答案

      根据 MSDN,在 .NET 4.0 中,基本上你不应该使用 ISerializable 来表示部分受信任的代码,而应该使用 ISafeSerializationData

      According to the MSDN, in .NET 4.0 basically you should not use ISerializable for partially trusted code, and instead you should use ISafeSerializationData

      引用自 https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization

      重要

      在 .NET Framework 4.0 之前的版本中,部分受信任程序集中的自定义用户数据的序列化是使用 GetObjectData 完成的.从 4.0 版开始,该方法使用 SecurityCriticalAttribute 属性进行标记,以防止在部分受信任的程序集中执行.要解决此问题,请实现 ISafeSerializationData 接口.

      In versions previous to .NET Framework 4.0, serialization of custom user data in a partially trusted assembly was accomplished using the GetObjectData. Starting with version 4.0, that method is marked with the SecurityCriticalAttribute attribute which prevents execution in partially trusted assemblies. To work around this condition, implement the ISafeSerializationData interface.

      所以如果你需要它,可能不是你想听到的,但我认为在继续使用 ISerializable 的同时没有任何办法解决它(除了回到 Level1代码> 安全性,您说过不想这样做).

      So probably not what you wanted to hear if you need it, but I don't think there's any way around it while keeping using ISerializable (other than going back to Level1 security, which you said you don't want to).

      PS:ISafeSerializationData 文档声明它仅用于例外,但它似乎并不那么具体,您可能想试一试......我基本上无法测试它与您的示例代码(除了删除 ISerializable 有效,但您已经知道了)...您必须查看 ISafeSerializationData 是否足够适合您.

      PS: the ISafeSerializationData docs state that it is just for exceptions, but it doesn't seem all that specific, you may want to give it a shot... I basically can't test it with your sample code (other than removing ISerializable works, but you knew that already)... you'll have to see if ISafeSerializationData suits you enough.

      PS2:SecurityCritical 属性不起作用,因为它在以部分信任模式(在 Level2 安全性上)加载程序集时会被忽略.您可以在示例代码中看到它,如果您在调用 ExecuteUntrustedCode 中的 target 变量之前调试它,它会将 IsSecurityTransparent 设置为 trueIsSecurityCriticalfalse 即使你用 SecurityCritical 属性标记方法)

      PS2: the SecurityCritical attribute doesn't work because it's ignored when the assembly is loaded in partial trust mode (on Level2 security). You can see it on your sample code, if you debug the target variable in ExecuteUntrustedCode right before invoking it, it'll have IsSecurityTransparent to true and IsSecurityCritical to false even if you mark the method with the SecurityCritical attribute)

      这篇关于如何在不违反继承安全规则的情况下在 .NET 4+ 中实现 ISerializable?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆