Bagaimana saya bisa menerapkan ISerializable di .NET 4+ tanpa melanggar aturan keamanan warisan?

109

Latar Belakang: Waktu Noda berisi banyak struct yang dapat bersambung. Meskipun saya tidak suka serialisasi biner, kami menerima banyak permintaan untuk mendukungnya, kembali ke timeline 1.x. Kami mendukungnya dengan mengimplementasikan ISerializableantarmuka.

Kami telah menerima laporan masalah baru-baru ini tentang Noda Time 2.x yang gagal dalam .NET Fiddle . Kode yang sama menggunakan Noda Time 1.x berfungsi dengan baik. Pengecualian yang diberikan adalah ini:

Aturan keamanan pewarisan dilanggar saat menimpa anggota: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Aksesibilitas keamanan dari metode penggantian harus sesuai dengan aksesibilitas keamanan dari metode yang akan diganti.

Saya telah mempersempit ini ke kerangka kerja yang ditargetkan: 1.x target. NET 3.5 (profil klien); 2.x menargetkan .NET 4.5. Mereka memiliki perbedaan besar dalam hal dukungan PCL vs .NET Core dan struktur file proyek, tetapi sepertinya ini tidak relevan.

Saya telah berhasil mereproduksi ini dalam proyek lokal, tetapi saya belum menemukan solusi untuk itu.

Langkah-langkah untuk mereproduksi di VS2017:

  • Buat solusi baru
  • Buat aplikasi konsol Windows klasik baru yang menargetkan .NET 4.5.1. Saya menyebutnya "CodeRunner".
  • Di properti proyek, buka Menandatangani dan menandatangani rakitan dengan kunci baru. Hapus centang persyaratan kata sandi, dan gunakan nama file kunci apa saja.
  • Tempel kode berikut untuk mengganti Program.cs. Ini adalah versi kode yang disingkat dalam contoh Microsoft ini . Saya telah menyimpan semua jalur yang sama, jadi jika Anda ingin kembali ke kode yang lebih lengkap, Anda tidak perlu mengubah apa pun.

Kode:

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);
    }  
}
  • Buat proyek lain yang disebut "UntrustedCode". Ini harus menjadi proyek Perpustakaan Kelas Desktop Klasik.
  • Tanda tangani majelis; Anda dapat menggunakan kunci baru atau yang sama seperti untuk CodeRunner. (Ini sebagian untuk meniru situasi Waktu Noda, dan sebagian lagi untuk membuat Analisis Kode senang.)
  • Tempel kode berikut Class1.cs(menimpa apa yang ada di sana):

Kode:

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();
        }
    }
}

Menjalankan proyek CodeRunner memberikan pengecualian berikut (diformat ulang agar mudah dibaca):

Unhandled Exception: System.Reflection.TargetInvocationException:
Exception telah dilemparkan oleh target pemanggilan.
--->
System.TypeLoadException:
Aturan keamanan warisan dilanggar saat mengganti anggota:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
Aksesibilitas keamanan
dari metode penggantian harus sesuai dengan aksesibilitas keamanan dari metode yang akan diganti.

Atribut yang diberi komentar menunjukkan hal-hal yang telah saya coba:

  • SecurityPermissiondirekomendasikan oleh dua artikel MS yang berbeda ( pertama , kedua ), meskipun menariknya mereka melakukan hal yang berbeda seputar implementasi antarmuka eksplisit / implisit
  • SecurityCriticaladalah apa yang saat ini Noda Waktu memiliki, dan apa jawaban ini pertanyaan ini menunjukkan
  • SecuritySafeCritical agak disarankan oleh pesan aturan Analisis Kode
  • Tanpa setiap atribut, aturan Analisis Kode senang - dengan baik SecurityPermissionatau SecurityCritical ini, aturan memberitahu Anda untuk menghapus atribut - kecuali Anda lakukan memiliki AllowPartiallyTrustedCallers. Mengikuti saran dalam kedua kasus tidak membantu.
  • Waktu Noda telah AllowPartiallyTrustedCallersditerapkan padanya; contoh di sini tidak berfungsi baik dengan atau tanpa atribut diterapkan.

Kode berjalan tanpa pengecualian jika saya menambahkan [assembly: SecurityRules(SecurityRuleSet.Level1)]ke UntrustedCodeassembly (dan menghapus komentar AllowPartiallyTrustedCallersatribut), tetapi saya yakin itu adalah solusi yang buruk untuk masalah yang dapat menghambat kode lain.

Saya sepenuhnya mengaku sangat tersesat dalam hal aspek keamanan .NET semacam ini. Jadi apa yang dapat saya lakukan untuk menargetkan .NET 4.5 namun mengizinkan tipe saya untuk diimplementasikan ISerializabledan masih digunakan di lingkungan seperti .NET Fiddle?

(Sementara saya menargetkan .NET 4.5, saya yakin itu adalah perubahan kebijakan keamanan .NET 4.0 yang menyebabkan masalah, oleh karena itu tagnya.)

Jon Skeet
sumber
Yang cukup menarik, penjelasan tentang perubahan pada model keamanan di 4.0 ini menunjukkan bahwa menghapus saja sudah cukup AllowPartiallyTrustedCallers, tetapi tampaknya tidak ada bedanya
Mathias R. Jessen

Jawaban:

56

Menurut MSDN , di .NET 4.0 pada dasarnya Anda tidak boleh menggunakan ISerializablekode tepercaya sebagian, dan sebaliknya Anda harus menggunakan ISafeSerializationData

Mengutip dari https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization

Penting

Dalam versi sebelumnya .NETFramework 4.0, serialisasi data pengguna kustom dalam rakitan tepercaya sebagian dicapai menggunakan GetObjectData. Dimulai dengan versi 4.0, metode tersebut ditandai dengan atribut SecurityCriticalAttribute yang mencegah eksekusi di rakitan tepercaya sebagian. Untuk mengatasi kondisi ini, implementasikan antarmuka ISafeSerializationData.

Jadi mungkin bukan yang ingin Anda dengar jika Anda membutuhkannya, tetapi menurut saya tidak ada cara lain untuk tetap menggunakannya ISerializable(selain kembali ke Level1keamanan, yang Anda katakan tidak Anda inginkan).

PS: ISafeSerializationDatadokumen menyatakan bahwa ini hanya untuk pengecualian, tetapi tampaknya tidak terlalu spesifik, Anda mungkin ingin mencobanya ... Saya pada dasarnya tidak dapat mengujinya dengan kode sampel Anda (selain menghapus ISerializablekarya, tetapi Anda sudah tahu itu) ... Anda harus melihat apakah ISafeSerializationDatacukup cocok untuk Anda.

PS2: SecurityCriticalatribut tidak bekerja karena diabaikan saat perakitan dimuat dalam mode kepercayaan parsial ( pada keamanan Level2 ). Anda bisa melihatnya pada contoh kode Anda, jika Anda debug targetvariabel dalam ExecuteUntrustedCodetepat sebelum memohon, itu harus IsSecurityTransparentuntuk truedan IsSecurityCriticaluntuk falsebahkan jika Anda menandai metode dengan SecurityCriticalatribut)

Jcl
sumber
Aha - terima kasih atas penjelasannya. Memalukan, pengecualian sangat menyesatkan di sini. Akan perlu memikirkan apa yang harus dilakukan ...
Jon Skeet
@JonSkeet Sejujurnya, saya akan membuang semua serialisasi biner ... tapi saya mengerti basis pengguna Anda mungkin tidak menyukainya
Jcl
Saya pikir kita harus melakukan itu - yang berarti pindah ke v3.0. Ini memiliki manfaat lain ... Saya perlu berkonsultasi dengan komunitas Waktu Noda.
Jon Skeet
12
@JonSkeet btw, jika Anda tertarik, artikel ini menjelaskan perbedaan antara keamanan level 1 dan level 2 (dan MENGAPA tidak berfungsi)
Jcl
8

Jawaban yang diterima begitu meyakinkan sehingga saya hampir percaya ini bukan bug. Tetapi setelah melakukan beberapa percobaan sekarang saya dapat mengatakan bahwa keamanan Level2 benar-benar kacau; setidaknya, ada sesuatu yang sangat mencurigakan.

Beberapa hari yang lalu saya mengalami masalah yang sama dengan perpustakaan saya. Saya dengan cepat membuat tes unit; namun, saya tidak dapat mereproduksi masalah yang saya alami di .NET Fiddle, sementara kode yang sama "berhasil" membuat pengecualian di aplikasi konsol. Akhirnya saya menemukan dua cara aneh untuk mengatasi masalah tersebut.

TL; DR : Ternyata jika Anda menggunakan jenis internal pustaka bekas dalam proyek konsumen Anda, maka kode tepercaya sebagian berfungsi seperti yang diharapkan: ia dapat membuat contoh ISerializableimplementasi (dan kode penting keamanan tidak dapat dipanggil secara langsung, tapi lihat di bawah). Atau, yang lebih konyol lagi, Anda dapat mencoba membuat kotak pasir lagi jika tidak berfungsi untuk pertama kalinya ...

Tapi mari kita lihat beberapa kode.

ClassLibrary.dll:

Mari kita pisahkan dua kasus: satu untuk kelas reguler dengan konten penting keamanan dan satu ISerializableimplementasi:

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) { }
}

Salah satu cara untuk mengatasi masalah tersebut adalah dengan menggunakan tipe internal dari perakitan konsumen. Tipe apa pun akan melakukannya; sekarang saya mendefinisikan atribut:

[AttributeUsage(AttributeTargets.All)]
internal class InternalTypeReferenceAttribute : Attribute
{
    public InternalTypeReferenceAttribute() { }
}

Dan atribut yang relevan diterapkan pada perakitan:

[assembly: InternalsVisibleTo("UnitTest, PublicKey=<your public key>")]
[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]

Tanda tangani majelis, terapkan kunci ke InternalsVisibleToatribut dan bersiap untuk proyek uji:

UnitTest.dll (menggunakan NUnit dan ClassLibrary):

Untuk menggunakan trik internal, perakitan pengujian harus ditandatangani juga. Atribut perakitan:

// 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]

Catatan : Atribut dapat diterapkan di mana saja. Dalam kasus saya, itu adalah metode di kelas tes acak yang membutuhkan beberapa hari untuk saya temukan.

Catatan 2 : Jika Anda menjalankan semua metode pengujian bersama-sama, mungkin saja pengujian tersebut akan lulus.

Kerangka kelas tes:

[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;
    }
}

Dan mari kita lihat kasus uji satu per satu

Kasus 1: Implementasi yang dapat dialihkan

Masalah yang sama seperti di pertanyaan. Tes lulus jika

  • InternalTypeReferenceAttribute diterapkan
  • sandbox dicoba dibuat berkali-kali (lihat kode)
  • atau, jika semua kasus uji dijalankan sekaligus dan ini bukan yang pertama

Jika tidak, akan muncul Inheritance security rules violated while overriding member...pengecualian yang sama sekali tidak pantas saat Anda membuat instance 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);
    }

}

Kasus 2: Kelas reguler dengan anggota penting keamanan

Tes lulus dalam kondisi yang sama seperti yang pertama. Namun, masalahnya sangat berbeda di sini: kode yang dipercaya sebagian dapat mengakses anggota penting keamanan secara langsung .

[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());
    }
}

Kasus 3-4: Versi kepercayaan penuh dari kasus 1-2

Demi kelengkapan, berikut adalah kasus yang sama dengan kasus di atas yang dijalankan dalam domain tepercaya sepenuhnya. Jika Anda menghapus [assembly: AllowPartiallyTrustedCallers]tes gagal karena Anda dapat mengakses kode kritis secara langsung (karena metode tidak lagi transparan secara default).

[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);
}

Epilog:

Tentu saja, ini tidak akan menyelesaikan masalah Anda dengan .NET Fiddle. Tapi sekarang saya akan sangat terkejut jika itu bukan bug dalam framework.

Pertanyaan terbesar bagi saya sekarang adalah bagian yang dikutip dalam jawaban yang diterima. Bagaimana mereka keluar dengan omong kosong ini? Ini ISafeSerializationDatajelas bukan solusi untuk apa pun: ini digunakan secara eksklusif oleh Exceptionkelas dasar dan jika Anda berlangganan SerializeObjectStateacara (mengapa itu bukan metode yang dapat diganti?), Maka negara juga akan dikonsumsi oleh Exception.GetObjectDataakhirnya.

The AllowPartiallyTrustedCallers/ SecurityCritical/ SecuritySafeCriticaltiga serangkai atribut dirancang untuk persis penggunaan yang ditunjukkan di atas. Tampaknya tidak masuk akal bagi saya bahwa kode tepercaya sebagian bahkan tidak dapat membuat contoh suatu tipe terlepas dari upaya menggunakan anggota penting keamanannya. Tapi itu adalah omong kosong yang lebih besar ( sebenarnya lubang keamanan ) bahwa kode yang dipercaya sebagian dapat mengakses metode kritis keamanan secara langsung (lihat kasus 2 ) sedangkan ini dilarang untuk metode transparan bahkan dari domain yang sepenuhnya tepercaya.

Jadi jika proyek konsumen Anda adalah tes atau perakitan terkenal lainnya, maka trik internal dapat digunakan dengan sempurna. Untuk .NET Fiddle dan lingkungan sandbox kehidupan nyata lainnya, satu-satunya solusi adalah kembali ke SecurityRuleSet.Level1hingga ini diperbaiki oleh Microsoft.


Update: Sebuah tiket Komunitas Pengembang telah dibuat untuk masalah ini.

György Kőszeg
sumber
2

Menurut MSDN lihat:

Bagaimana Cara Memperbaiki Pelanggaran?

Untuk memperbaiki pelanggaran aturan ini, buat metode GetObjectData terlihat dan dapat diganti dan pastikan semua bidang contoh disertakan dalam proses serialisasi atau secara eksplisit ditandai dengan atribut NonSerializedAttribute .

Berikut contoh perbaikan dua pelanggaran sebelumnya dengan menyediakan sebuah implementasi overrideable dari ISerializable.GetObjectData di kelas Book dan dengan menyediakan sebuah implementasi dari ISerializable.GetObjectData pada kelas Perpustakaan.

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);
        }
    }
}
5377037
sumber
2
Artikel yang Anda tautkan adalah untuk CA2240, yang tidak dipecat - kode tidak melanggarnya. Ini adalah struct, jadi tersegel secara efektif; tidak ada bidang apa pun; itu mengimplementasikan GetObjectDatasecara eksplisit, tetapi melakukannya secara implisit tidak membantu.
Jon Skeet
15
Tentu, dan terima kasih telah mencoba - tetapi saya sedang menjelaskan mengapa itu tidak berhasil. (Dan sebagai rekomendasi - untuk sesuatu yang rumit seperti ini, di mana pertanyaannya menyertakan contoh yang dapat diverifikasi, ada baiknya untuk mencoba menerapkan perbaikan yang disarankan dan melihat apakah itu benar - benar membantu.)
Jon Skeet