ฉันจะใช้ ISerializable ใน. NET 4+ โดยไม่ละเมิดกฎความปลอดภัยในการสืบทอดได้อย่างไร


110

ความเป็นมา: Noda Timeมีโครงสร้างที่ต่อเนื่องกันได้มากมาย แม้ว่าฉันไม่ชอบการทำให้เป็นอนุกรมไบนารี แต่เราก็ได้รับคำขอให้สนับสนุนมากมายกลับมาในไทม์ไลน์ 1.x เราสนับสนุนโดยใช้ISerializableอินเทอร์เฟซ

เราได้รับเมื่อเร็ว ๆ นี้รายงานปัญหาของ Noda เวลา 2.x ล้มเหลวภายใน .NET ซอ รหัสเดียวกันที่ใช้ Noda Time 1.x ทำงานได้ดี ข้อยกเว้นที่เกิดขึ้นคือ:

กฎความปลอดภัยในการสืบทอดถูกละเมิดในขณะที่แทนที่สมาชิก: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext) " ความสามารถในการเข้าถึงความปลอดภัยของวิธีการลบล้างต้องตรงกับความสามารถในการเข้าถึงความปลอดภัยของวิธีการที่ถูกลบล้าง

ฉันได้ จำกัด สิ่งนี้ให้แคบลงเป็นกรอบงานที่กำหนดเป้าหมาย: 1.x เป้าหมาย. NET 3.5 (โปรไฟล์ลูกค้า); 2.x เป้าหมาย. NET 4.5 พวกเขามีความแตกต่างอย่างมากในแง่ของการสนับสนุน PCL เทียบกับ. NET Core และโครงสร้างไฟล์โครงการ แต่ดูเหมือนว่าจะไม่เกี่ยวข้อง

ฉันสามารถทำซ้ำสิ่งนี้ในโครงการในพื้นที่ได้ แต่ฉันไม่พบวิธีแก้ปัญหา

ขั้นตอนในการสร้างซ้ำใน VS2017:

  • สร้างโซลูชันใหม่
  • สร้างแอปพลิเคชันคอนโซล Windows แบบคลาสสิกใหม่ที่กำหนดเป้าหมายเป็น. NET 4.5.1 ฉันเรียกมันว่า "CodeRunner"
  • ในคุณสมบัติโปรเจ็กต์ไปที่การเซ็นชื่อและเซ็นชื่อแอสเซมบลีด้วยคีย์ใหม่ ยกเลิกข้อกำหนดรหัสผ่านและใช้ชื่อไฟล์คีย์ใดก็ได้
  • Program.csวางรหัสต่อไปนี้แทน นี่คือโค้ดเวอร์ชันย่อในตัวอย่าง Microsoftนี้ ฉันได้กำหนดเส้นทางทั้งหมดไว้เหมือนเดิมดังนั้นหากคุณต้องการกลับไปใช้โค้ดที่สมบูรณ์กว่านี้คุณไม่จำเป็นต้องเปลี่ยนอะไรอีก

รหัส:

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" นี่ควรเป็นโปรเจ็กต์ Classic Desktop Class Library
  • ลงนามการชุมนุม คุณสามารถใช้คีย์ใหม่หรือคีย์เดียวกับ CodeRunner (นี่เป็นบางส่วนเพื่อเลียนแบบสถานการณ์ Noda Time และอีกส่วนหนึ่งเพื่อให้การวิเคราะห์โค้ดมีความสุข)
  • วางรหัสต่อไปนี้ใน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 ให้ข้อยกเว้นต่อไปนี้ (จัดรูปแบบใหม่เพื่อให้อ่านง่าย):

Unhandled Exception: System.Reflection.TargetInvocationException:
Exception ถูกโยนโดยเป้าหมายของการเรียกใช้
--->
System.TypeLoadException:
กฎความปลอดภัยในการสืบทอดถูกละเมิดในขณะที่แทนที่สมาชิก:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (... )
ความสามารถในการเข้าถึงความปลอดภัยของวิธีการลบล้างต้องตรงกับความ
สามารถในการเข้าถึงความปลอดภัยของวิธีการที่ถูกลบล้าง

แอตทริบิวต์ที่แสดงความคิดเห็นแสดงสิ่งที่ฉันได้ลอง:

  • SecurityPermissionแนะนำโดยสองบทความ MS ที่แตกต่างกัน ( ครั้งแรก , ครั้งที่สอง ) แม้ว่าที่น่าสนใจที่พวกเขาทำสิ่งที่แตกต่างอย่างชัดเจนรอบใช้อินเตอร์เฟซ / ปริยาย
  • SecurityCriticalคือสิ่งที่ Noda Time มีอยู่ในปัจจุบันและเป็นสิ่งที่คำตอบของคำถามนี้แนะนำ
  • SecuritySafeCritical ค่อนข้างแนะนำโดยข้อความกฎการวิเคราะห์โค้ด
  • โดยไม่ต้องใด ๆแอตทริบิวต์กฎการวิเคราะห์รหัสมีความสุข - ด้วยSecurityPermissionหรือSecurityCritical ปัจจุบันกฎบอกให้คุณลบแอตทริบิวต์ - ถ้าคุณทำAllowPartiallyTrustedCallersมี การทำตามคำแนะนำในทั้งสองกรณีไม่ช่วยอะไร
  • Noda Time AllowPartiallyTrustedCallersใช้กับมันแล้ว ตัวอย่างที่นี่ใช้ไม่ได้ทั้งโดยมีหรือไม่มีแอตทริบิวต์ที่ใช้

รหัสจะทำงานโดยไม่มีข้อยกเว้นหากฉันเพิ่มลง[assembly: SecurityRules(SecurityRuleSet.Level1)]ในUntrustedCodeแอสเซมบลี (และไม่ใส่เครื่องหมายกำกับAllowPartiallyTrustedCallersแอตทริบิวต์) แต่ฉันเชื่อว่านั่นเป็นวิธีแก้ปัญหาที่ไม่ดีสำหรับปัญหาที่อาจขัดขวางโค้ดอื่น ๆ

ฉันยอมรับอย่างเต็มที่ว่าหายไปเมื่อพูดถึงความปลอดภัยประเภทนี้ของ. NET ดังนั้นสิ่งที่สามารถฉันทำเพื่อกำหนดเป้าหมาย .NET 4.5 และยังอนุญาตให้มีชนิดของฉันในการดำเนินการISerializableและยังคงถูกนำมาใช้ในสภาพแวดล้อมเช่น .NET ซอ?

(ในขณะที่ฉันกำหนดเป้าหมาย. NET 4.5 ฉันเชื่อว่าเป็นการเปลี่ยนแปลงนโยบายความปลอดภัย. NET 4.0 ที่ทำให้เกิดปัญหาด้วยเหตุนี้แท็ก)


ที่น่าสนใจก็คือคำอธิบายเกี่ยวกับการเปลี่ยนแปลงรูปแบบความปลอดภัยใน 4.0 นี้ชี้ให้เห็นว่าการลบAllowPartiallyTrustedCallersควรทำตามเคล็ดลับ แต่ดูเหมือนจะไม่สร้างความแตกต่าง
Mathias R. Jessen

คำตอบ:


56

ตาม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งานต่อไป (นอกเหนือจากการกลับไปที่Level1ความปลอดภัยซึ่งคุณบอกว่าคุณไม่ต้องการ)

PS: ISafeSerializationDataเอกสารระบุว่าเป็นเพียงข้อยกเว้น แต่ดูเหมือนจะไม่เฉพาะเจาะจงทั้งหมดคุณอาจต้องการลองดู ... โดยทั่วไปฉันไม่สามารถทดสอบด้วยรหัสตัวอย่างของคุณได้ (นอกเหนือจากการลบISerializableผลงาน แต่คุณรู้แล้ว) ... คุณจะต้องดูว่าISafeSerializationDataเหมาะสมกับคุณเพียงพอหรือไม่

PS2: SecurityCriticalแอตทริบิวต์ไม่ทำงานเนื่องจากถูกละเว้นเมื่อแอสเซมบลีถูกโหลดในโหมดความไว้วางใจบางส่วน ( ในการรักษาความปลอดภัยระดับ 2 ) คุณสามารถดูได้ในตัวอย่างรหัสของคุณถ้าคุณแก้ปัญหาtargetตัวแปรในExecuteUntrustedCodeทางด้านขวาก่อนที่จะอัญเชิญมันจะต้องIsSecurityTransparentไปtrueและIsSecurityCriticalไปfalseแม้ว่าคุณจะทำเครื่องหมายวิธีการที่มีSecurityCriticalแอตทริบิวต์)


Aha - ขอบคุณสำหรับคำอธิบาย ความอัปยศข้อยกเว้นทำให้เข้าใจผิดที่นี่ จะต้องคิดว่าจะทำอย่างไร ...
Jon Skeet

@JonSkeet สุจริตฉันทิ้งอนุกรมไบนารีทั้งหมดไปด้วยกัน ... แต่ฉันเข้าใจว่าฐานผู้ใช้ของคุณอาจไม่ชอบ
Jcl

ฉันคิดว่าเราจะต้องทำอย่างนั้น - ซึ่งหมายถึงการย้ายไปที่ v3.0 มันมีประโยชน์อื่น ๆ แม้ว่า ... ฉันจะต้องปรึกษาชุมชนโนดะไทม์
Jon Skeet

12
@JonSkeet btw หากคุณสนใจบทความนี้จะอธิบายความแตกต่างระหว่างการรักษาความปลอดภัยระดับ 1 และระดับ 2 (และทำไมถึงใช้ไม่ได้)
Jcl

8

คำตอบที่ยอมรับนั้นน่าเชื่อมากจนฉันเกือบจะเชื่อว่านี่ไม่ใช่ข้อบกพร่อง แต่หลังจากทำการทดลองบางอย่างตอนนี้ฉันสามารถพูดได้ว่าการรักษาความปลอดภัยระดับ 2 นั้นเป็นเรื่องที่วุ่นวาย อย่างน้อยก็มีบางอย่างที่คาวมาก

สองสามวันก่อนฉันพบปัญหาเดียวกันกับห้องสมุดของฉัน ฉันสร้างแบบทดสอบหน่วยอย่างรวดเร็ว อย่างไรก็ตามฉันไม่สามารถจำลองปัญหาที่ฉันพบใน. NET Fiddle ได้ในขณะที่รหัสเดียวกัน "ประสบความสำเร็จ" ทำให้เกิดข้อยกเว้นในแอปคอนโซล ในที่สุดฉันก็พบวิธีแปลก ๆ สองวิธีในการเอาชนะปัญหานี้

TL; DR : ปรากฎว่าหากคุณใช้ประเภทภายในของไลบรารีที่ใช้ในโครงการสำหรับผู้บริโภคของคุณรหัสที่เชื่อถือได้บางส่วนจะทำงานตามที่คาดไว้: สามารถสร้างอินสแตนซ์ของการISerializableใช้งานได้ (และไม่สามารถเรียกรหัสสำคัญด้านความปลอดภัยได้โดยตรง แต่ดูด้านล่าง) หรือที่ไร้สาระยิ่งกว่านั้นคุณสามารถลองสร้างแซนด์บ็อกซ์อีกครั้งหากไม่ได้ผลในครั้งแรก ...

แต่เรามาดูโค้ดกัน

ClassLibrary.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แอตทริบิวต์และเตรียมพร้อมสำหรับโครงการทดสอบ:

UnitTest.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: การใช้งานที่ปรับเปลี่ยนได้

ประเด็นเดียวกับในคำถาม การทดสอบจะผ่านถ้า

  • 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: เวอร์ชันที่เชื่อถือได้เต็มรูปแบบของ case 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);
}

บทส่งท้าย:

แน่นอนว่านี่จะไม่ช่วยแก้ปัญหาของคุณกับ. NET Fiddle แต่ตอนนี้ฉันจะแปลกใจมากถ้ามันไม่ใช่จุดบกพร่องในกรอบ

คำถามที่ใหญ่ที่สุดสำหรับฉันตอนนี้คือส่วนที่ยกมาในคำตอบที่ยอมรับ พวกเขาออกมาไร้สาระขนาดนี้ได้อย่างไร? ISafeSerializationDataเป็นอย่างชัดเจนไม่ได้แก้ปัญหาอะไร: มันถูกนำมาใช้โดยเฉพาะฐานExceptionการเรียนและถ้าคุณซื้อSerializeObjectStateเหตุการณ์ (ทำไมไม่ว่าจะเป็นวิธีการที่ overridable?) แล้วรัฐก็จะถูกบริโภคโดยException.GetObjectDataในท้ายที่สุด

AllowPartiallyTrustedCallers/ SecurityCritical/ SecuritySafeCriticalเสือของคุณลักษณะที่ได้รับการออกแบบสำหรับการใช้งานตรงตามที่แสดงไว้ข้างต้น ดูเหมือนเป็นเรื่องไร้สาระสำหรับฉันที่รหัสที่เชื่อถือได้บางส่วนไม่สามารถสร้างอินสแตนซ์ประเภทได้ไม่ว่าจะพยายามใช้สมาชิกที่สำคัญด้านความปลอดภัยก็ตาม แต่มันเป็นเรื่องไร้สาระที่ยิ่งใหญ่กว่านั้น ( จริง ๆ แล้วคือช่องโหว่ด้านความปลอดภัย ) ที่รหัสที่เชื่อถือได้บางส่วนอาจเข้าถึงวิธีการที่สำคัญด้านความปลอดภัยโดยตรง (ดูกรณีที่ 2 ) ในขณะที่วิธีนี้ไม่ได้รับอนุญาตสำหรับวิธีการที่โปร่งใสแม้จะมาจากโดเมนที่เชื่อถือได้ทั้งหมด

ดังนั้นหากโครงการผู้บริโภคของคุณเป็นการทดสอบหรือการประกอบอื่นที่มีชื่อเสียงก็สามารถใช้เคล็ดลับภายในได้อย่างสมบูรณ์แบบ สำหรับ. NET Fiddle และสภาพแวดล้อมแซนด์บ็อกซ์ในชีวิตจริงอื่น ๆ โซลูชันเดียวคือการเปลี่ยนกลับไปSecurityRuleSet.Level1จนกว่าจะได้รับการแก้ไขโดย Microsoft


ปรับปรุง: ตั๋วสำหรับนักพัฒนาชุมชนได้รับการสร้างขึ้นสำหรับปัญหา


2

ตามMSDNดู:

จะแก้ไขการละเมิดได้อย่างไร?

ในการแก้ไขการละเมิดกฎนี้ให้ทำให้เมธอดGetObjectDataมองเห็นได้และสามารถเขียนทับได้และตรวจสอบให้แน่ใจว่าฟิลด์อินสแตนซ์ทั้งหมดรวมอยู่ในกระบวนการทำให้เป็นอนุกรมหรือทำเครื่องหมายอย่างชัดเจนด้วยแอตทริบิวต์NonSerializedAttribute

ตัวอย่างต่อไปนี้แก้ไขการละเมิดสองครั้งก่อนหน้านี้โดยจัดเตรียมการนำไปใช้งานที่สามารถลบล้างได้ของ ISerializable.GetObjectData บนคลาสหนังสือและโดยการจัดเตรียมการใช้งาน 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
บทความที่คุณเชื่อมโยงมีไว้สำหรับ CA2240 ซึ่งไม่ได้เริ่มทำงาน - รหัสไม่ได้ละเมิด มันเป็นโครงสร้างดังนั้นจึงปิดผนึกอย่างมีประสิทธิภาพ มันไม่มีฟิลด์ใด ๆ มันใช้GetObjectDataอย่างชัดเจน แต่การทำเช่นนั้นโดยปริยายไม่ได้ช่วยอะไร
Jon Skeet

15
แน่นอนและขอบคุณที่พยายาม - แต่ฉันกำลังอธิบายว่าเหตุใดจึงไม่ได้ผล (และเป็นคำแนะนำ - สำหรับสิ่งที่ยุ่งยากเช่นนี้ซึ่งคำถามมีตัวอย่างที่ตรวจสอบได้คุณควรลองใช้การแก้ไขที่แนะนำและดูว่าช่วยได้จริงหรือไม่)
Jon Skeet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.