背景:Noda Time包含许多内容
可序列化的结构。虽然我不喜欢二进制序列化,但我们
在1.x时间表中收到了许多支持它的请求。
我们通过实施ISerializable
接口来支持它。
我们最近收到了Noda的issue report 时间2.x failing within .NET Fiddle。使用Noda的相同代码 时间1.x工作正常。抛出的异常是:
覆盖成员时违反了继承安全规则: ' NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)&#39 ;.安全 覆盖方法的可访问性必须与安全性相匹配 被覆盖的方法的可访问性。
我已将其缩小到目标框架:1.x 目标.NET 3.5(客户端配置文件); 2.x的目标是.NET 4.5。他们有 支持PCL与.NET Core和 项目文件结构,但看起来这是无关紧要的。
我设法在当地项目中重现了这一点,但我还没有 找到了解决方案。
在VS2017中重现的步骤:
Program.cs
。这是个
this Microsoft
sample中代码的缩写版本。
我保持所有路径都一样,所以如果你想回到路上
更全面的代码,您不应该更改任何其他内容。代码:
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);
}
}
Class1.cs
(覆盖其中的内容):代码:
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项目会产生以下异常(为了便于阅读而重新格式化):
未处理的异常:System.Reflection.TargetInvocationException:
调用目标引发了异常 ---&GT;
System.TypeLoadException:
覆盖成员时违反了继承安全规则:
&#39; UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData(...)
覆盖方法的安全可访问性必须与安全性相匹配 被覆盖的方法的可访问性。
已注释掉的属性显示了我尝试过的内容:
SecurityPermission
,
second),尽管如此
有趣的是,他们围绕显式/隐式接口实现做了不同的事情SecurityCritical
是Noda Time目前拥有的,this question's answer建议SecuritySafeCritical
SecurityPermission
或SecurityCritical
目前,规则告诉您删除属性 - 除非您做有AllowPartiallyTrustedCallers
。在这两种情况下提出建议都没有帮助。AllowPartiallyTrustedCallers
;这里的示例在使用或不应用属性的情况下都不起作用。如果我将[assembly: SecurityRules(SecurityRuleSet.Level1)]
添加到UntrustedCode
程序集(并取消注释AllowPartiallyTrustedCallers
属性),代码会毫无例外地运行,但我相信这个问题解决不好这可能会妨碍其他代码。
我完全承认在谈到这种情况时会非常迷失
.NET的安全方面。那么可以我做的目标是.NET 4.5和
允许我的类型实现ISerializable
并仍在使用中
.NET Fiddle等环境?
(虽然我的目标是.NET 4.5,但我相信它导致了问题的.NET 4.0安全策略更改,因此标记。)
答案 0 :(得分:44)
根据the MSDN,在.NET 4.0中,基本上不应将ISerializable
用于部分受信任的代码,而应使用ISafeSerializationData
引自https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
重要
在.NET Framework 4.0之前的版本中,使用GetObjectData完成部分受信任程序集中的自定义用户数据的序列化。从版本4.0开始,该方法使用SecurityCriticalAttribute属性进行标记,该属性可防止在部分受信任的程序集中执行。要解决此问题,请实现ISafeSerializationData接口。
如果你需要它,可能不是你想听到的,但是我不认为在保持使用ISerializable
的同时可以解决它(除了回到{{1安全性,你说你不想)。
PS:Level1
文档声明它仅用于例外,但它看起来并不是那么具体,你可能想试一试......我基本上不能这样做用你的示例代码测试它(除了删除ISafeSerializationData
之外的工作,但你已经知道了)......你必须看看ISerializable
是否适合你。
PS2:ISafeSerializationData
属性不起作用,因为在部分信任模式( on Level2 security )上加载程序集时会忽略它。您可以在示例代码中看到它,如果您在调用它之前调试SecurityCritical
中的target
变量,它会ExecuteUntrustedCode
到IsSecurityTransparent
和{{ 1}}到true
,即使您使用IsSecurityCritical
属性标记方法
答案 1 :(得分:2)
根据MSDN见:
如何修复违规行为?
要修复违反此规则的行为,请使GetObjectData方法可见且可覆盖,并确保所有实例字段都包含在序列化过程中或明确标记为NonSerializedAttribute属性。
以下example通过在Book类上提供ISerializable.GetObjectData的可重写实现并在Library类上提供ISerializable.GetObjectData的实现来修复前两个违规。
using System;
using System.Security.Permissions;
using System.Runtime.Serialization;
namespace Samples2
{
[Serializable]
public class Book : ISerializable
{
private readonly string _Title;
public Book(string title)
{
if (title == null)
throw new ArgumentNullException("title");
_Title = title;
}
protected Book(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new ArgumentNullException("info");
_Title = info.GetString("Title");
}
public string Title
{
get { return _Title; }
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Title", _Title);
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new ArgumentNullException("info");
GetObjectData(info, context);
}
}
[Serializable]
public class LibraryBook : Book
{
private readonly DateTime _CheckedOut;
public LibraryBook(string title, DateTime checkedOut)
: base(title)
{
_CheckedOut = checkedOut;
}
protected LibraryBook(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_CheckedOut = info.GetDateTime("CheckedOut");
}
public DateTime CheckedOut
{
get { return _CheckedOut; }
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("CheckedOut", _CheckedOut);
}
}
}
答案 2 :(得分:1)
被接受的答案令人信服,以至于我几乎相信这不是一个错误。但是,经过一些实验之后,我可以说Level2安全性是一团糟。至少,有些东西真的很腥。
几天前,我遇到了与图书馆同样的问题。我很快创建了一个单元测试;但是,我无法重现我在.NET Fiddle中遇到的问题,而相同的代码“成功”在控制台应用程序中引发了异常。最终,我找到了两种解决该问题的怪异方法。
TL; DR :事实证明,如果您在使用者项目中使用内部库类型的旧库,那么部分受信任的代码将按预期工作:它可以实例化ISerializable
实现(并且不能直接调用安全性至关重要的代码,但请参见下文)。或者,更荒谬的是,如果第一次无法使用沙箱,则可以尝试再次创建它。
但是让我们看一些代码。
让我们分开两种情况:一种是具有安全关键内容的常规类,另一种是ISerializable
实现:
public class CriticalClass
{
public void SafeCode() { }
[SecurityCritical]
public void CriticalCode() { }
[SecuritySafeCritical]
public void SafeEntryForCriticalCode() => CriticalCode();
}
[Serializable]
public class SerializableCriticalClass : CriticalClass, ISerializable
{
public SerializableCriticalClass() { }
private SerializableCriticalClass(SerializationInfo info, StreamingContext context) { }
[SecurityCritical]
public void GetObjectData(SerializationInfo info, StreamingContext context) { }
}
解决该问题的一种方法是使用使用者程序集中的内部类型。任何类型都可以做到;现在我定义一个属性:
[AttributeUsage(AttributeTargets.All)]
internal class InternalTypeReferenceAttribute : Attribute
{
public InternalTypeReferenceAttribute() { }
}
并将相关属性应用于装配:
[assembly: InternalsVisibleTo("UnitTest, PublicKey=<your public key>")]
[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
签名程序集,将密钥应用于InternalsVisibleTo
属性并准备测试项目:
要使用内部技巧,还应对测试程序集进行签名。组装属性:
// Just to make the tests security transparent by default. This helps to test the full trust behavior.
[assembly: AllowPartiallyTrustedCallers]
// !!! Comment this line out and the partial trust test cases may fail for the fist time !!!
[assembly: InternalTypeReference]
注意:该属性可以应用于任何地方。就我而言,它是在随机测试课程中的一种方法上花费了我几天的时间。
注释2 :如果同时运行所有测试方法,则可能会通过测试。
测试类的骨架:
[TestFixture]
public class SecurityCriticalAccessTest
{
private partial class Sandbox : MarshalByRefObject
{
}
private static AppDomain CreateSandboxDomain(params IPermission[] permissions)
{
var evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
var permissionSet = GetPermissionSet(permissions);
var setup = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
};
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var strongNames = new List<StrongName>();
foreach (Assembly asm in assemblies)
{
AssemblyName asmName = asm.GetName();
strongNames.Add(new StrongName(new StrongNamePublicKeyBlob(asmName.GetPublicKey()), asmName.Name, asmName.Version));
}
return AppDomain.CreateDomain("SandboxDomain", evidence, setup, permissionSet, strongNames.ToArray());
}
private static PermissionSet GetPermissionSet(IPermission[] permissions)
{
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
var result = SecurityManager.GetStandardSandbox(evidence);
foreach (var permission in permissions)
result.AddPermission(permission);
return result;
}
}
让我们一个接一个地查看测试用例
与问题中的问题相同。如果通过
,则测试通过InternalTypeReferenceAttribute
已应用否则,实例化Inheritance security rules violated while overriding member...
时会出现完全不合适的SerializableCriticalClass
异常。
[Test]
[SecuritySafeCritical] // for Activator.CreateInstance
public void SerializableCriticalClass_PartialTrustAccess()
{
var domain = CreateSandboxDomain(
new SecurityPermission(SecurityPermissionFlag.SerializationFormatter), // BinaryFormatter
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
var handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
var sandbox = (Sandbox)handle.Unwrap();
try
{
sandbox.TestSerializableCriticalClass();
return;
}
catch (Exception e)
{
// without [InternalTypeReference] it may fail for the first time
Console.WriteLine($"1st try failed: {e.Message}");
}
domain = CreateSandboxDomain(
new SecurityPermission(SecurityPermissionFlag.SerializationFormatter), // BinaryFormatter
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
sandbox = (Sandbox)handle.Unwrap();
sandbox.TestSerializableCriticalClass();
Assert.Inconclusive("Meh... succeeded only for the 2nd try");
}
private partial class Sandbox
{
public void TestSerializableCriticalClass()
{
Assert.IsFalse(AppDomain.CurrentDomain.IsFullyTrusted);
// ISerializable implementer can be created.
// !!! May fail for the first try if the test does not use any internal type of the library. !!!
var critical = new SerializableCriticalClass();
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// Critical method cannot be called directly by a transparent method
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
Assert.Throws<MethodAccessException>(() => critical.GetObjectData(null, new StreamingContext()));
// BinaryFormatter calls the critical method via a safe route (SerializationFormatter permission is required, though)
new BinaryFormatter().Serialize(new MemoryStream(), critical);
}
}
测试在与第一个相同的条件下通过。但是,这里的问题完全不同: 部分受信任的代码可能直接访问安全关键成员 。
[Test]
[SecuritySafeCritical] // for Activator.CreateInstance
public void CriticalClass_PartialTrustAccess()
{
var domain = CreateSandboxDomain(
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess), // Assert.IsFalse
new EnvironmentPermission(PermissionState.Unrestricted)); // Assert.Throws (if fails)
var handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
var sandbox = (Sandbox)handle.Unwrap();
try
{
sandbox.TestCriticalClass();
return;
}
catch (Exception e)
{
// without [InternalTypeReference] it may fail for the first time
Console.WriteLine($"1st try failed: {e.Message}");
}
domain = CreateSandboxDomain(
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
sandbox = (Sandbox)handle.Unwrap();
sandbox.TestCriticalClass();
Assert.Inconclusive("Meh... succeeded only for the 2nd try");
}
private partial class Sandbox
{
public void TestCriticalClass()
{
Assert.IsFalse(AppDomain.CurrentDomain.IsFullyTrusted);
// A type containing critical methods can be created
var critical = new CriticalClass();
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// Critical method cannot be called directly by a transparent method
// !!! May fail for the first time if the test does not use any internal type of the library. !!!
// !!! Meaning, a partially trusted code has more right than a fully trusted one and is !!!
// !!! able to call security critical method directly. !!!
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
}
}
为完整起见,这里的情况与上述完全信任域中执行的情况相同。如果删除[assembly: AllowPartiallyTrustedCallers]
,则测试将失败,因为您可以直接访问关键代码(因为默认情况下该方法不再透明)。
[Test]
public void CriticalClass_FullTrustAccess()
{
Assert.IsTrue(AppDomain.CurrentDomain.IsFullyTrusted);
// A type containing critical methods can be created
var critical = new CriticalClass();
// Critical method cannot be called directly by a transparent method
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
}
[Test]
public void SerializableCriticalClass_FullTrustAccess()
{
Assert.IsTrue(AppDomain.CurrentDomain.IsFullyTrusted);
// ISerializable implementer can be created
var critical = new SerializableCriticalClass();
// Critical method cannot be called directly by a transparent method (see also AllowPartiallyTrustedCallersAttribute)
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
Assert.Throws<MethodAccessException>(() => critical.GetObjectData(null, default(StreamingContext)));
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// BinaryFormatter calls the critical method via a safe route
new BinaryFormatter().Serialize(new MemoryStream(), critical);
}
当然,这不能解决.NET Fiddle的问题。但是现在,如果它不是框架中的错误,我将感到非常惊讶。
对我来说,最大的问题是已接受答案中的引用部分。他们是怎么胡说八道的? ISafeSerializationData
显然不能解决任何问题:它仅由基类Exception
使用,并且如果您订阅了SerializeObjectState
事件(为什么不是一个可重写的方法?),那么状态最终也会由Exception.GetObjectData
消耗。
属性AllowPartiallyTrustedCallers
/ SecurityCritical
/ SecuritySafeCritical
专为上面显示的用途而设计。在我看来,完全不受信任的是,无论使用其安全关键成员的尝试如何,部分受信任的代码甚至都无法实例化类型。但这是一个更大的废话(实际上是一个安全漏洞),部分受信任的代码可以直接访问安全关键方法(请参阅情况2 ),但是为了透明起见,这是禁止的甚至来自完全可信域的方法。
因此,如果您的使用者项目是测试或其他知名组件,则可以完美使用内部技巧。对于.NET Fiddle和其他现实生活中的沙盒环境,唯一的解决方案是还原为SecurityRuleSet.Level1
,直到Microsoft修复此问题为止。
更新:为此问题创建了一个Developer Community ticket。