公认的答案是如此令人信服,以至于我几乎相信这不是一个bug。但在做了一些实验之后,现在我可以说Level2安全性是一团乱麻;至少,有些事情真的很可疑。
几天前,我的图书馆也遇到了同样的问题。我很快创建了一个单元测试;然而,我无法重现我在中遇到的问题。NET Fiddle,而同一代码“成功”在控制台应用程序中引发了异常。最后,我找到了两种奇怪的方法来解决这个问题。
TL;博士
:事实证明
如果在使用者项目中使用已用库的内部类型,则部分受信任的代码将按预期工作:它能够实例化
ISerializable
实施
(并且不能直接调用安全关键代码,但请参见下文)。或者,更荒谬的是,如果沙盒第一次不起作用,您可以尝试再次创建沙盒。。。
但让我们看看一些代码。
类库。dll:
让我们将两种情况分开:一种是具有安全关键内容的常规类,另一种是
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
属性并准备测试项目:
单元测试。dll(使用NUnit和ClassLibrary):
要使用内部技巧,测试组件也应签名。程序集属性:
// 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;
}
}
让我们逐一查看测试用例
案例1:ISerializable实现
与问题中的问题相同。如果满足以下条件,则测试通过
-
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);
}
}
案例2:具有安全关键成员的常规类
试验在与第一次相同的条件下通过。然而,这里的问题完全不同:
部分受信任的代码可以直接访问安全关键成员
.
[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());
}
}
案例3-4:案例1-2的完全信任版本
为了完整起见,这里的情况与上面在完全受信任的域中执行的情况相同。如果删除
[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);
}
后记:
当然,这并不能解决您的问题。网提琴。但是现在,如果不是框架中的bug,我会非常惊讶。
我现在最大的问题是被接受的答案中引用的部分。他们怎么会说出这些胡说八道?这个
ISafeSerializationData
显然不是解决任何问题的方案:它是由基地独家使用的
Exception
类,如果您订阅
SerializeObjectState
事件(为什么这不是一个可重写的方法?),然后状态也将被
Exception.GetObjectData
最后
这个
AllowPartiallyTrustedCallers
/
SecurityCritical
/
SecuritySafeCritical
三重属性的设计正好符合上面所示的用法。在我看来,一个部分受信任的代码甚至不能实例化一个类型,而不管是否尝试使用它的安全关键成员,这完全是胡说八道。但这是一个更大的胡说八道(a
安全漏洞
实际上)部分受信任的代码可以直接访问安全关键方法(请参见
案例2
)然而,即使是来自完全受信任域的透明方法也禁止这样做。
因此,如果您的消费者项目是一个测试或另一个众所周知的组件,那么内部技巧可以完美地使用。对于NET FIDLE和其他现实生活中的沙盒环境唯一的解决方案是恢复到
SecurityRuleSet.Level1
直到Microsoft修复此问题。
更新:
A.
Developer Community ticket
已为此问题创建。