Wie kann ich ISerializable in .NET 4+ implementieren, ohne die Vererbungssicherheitsregeln zu verletzen?

109

Hintergrund: Noda Time enthält viele serialisierbare Strukturen. Obwohl ich die binäre Serialisierung nicht mag, haben wir viele Anfragen erhalten, sie zu unterstützen, zurück in der 1.x-Timeline. Wir unterstützen es durch die Implementierung der ISerializableSchnittstelle.

Wir haben kürzlich einen Problembericht erhalten, in dem Noda Time 2.x in .NET Fiddle fehlschlägt . Der gleiche Code mit Noda Time 1.x funktioniert einwandfrei. Die Ausnahme ist folgende:

Vererbungssicherheitsregeln beim Überschreiben des Mitglieds verletzt: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Die Sicherheitszugänglichkeit der überschreibenden Methode muss mit der Sicherheitszugänglichkeit der überschriebenen Methode übereinstimmen.

Ich habe dies auf das Framework eingegrenzt, auf das abgezielt wird: 1.x zielt auf .NET 3.5 (Client-Profil) ab; 2.x zielt auf .NET 4.5 ab. Sie haben große Unterschiede in Bezug auf die Unterstützung von PCL gegenüber .NET Core und die Struktur der Projektdatei, aber es sieht so aus, als ob dies irrelevant ist.

Ich habe es geschafft, dies in einem lokalen Projekt zu reproduzieren, aber ich habe keine Lösung dafür gefunden.

Schritte zum Reproduzieren in VS2017:

  • Erstellen Sie eine neue Lösung
  • Erstellen Sie eine neue klassische Windows-Konsolenanwendung für .NET 4.5.1. Ich habe es "CodeRunner" genannt.
  • Gehen Sie in den Projekteigenschaften zu Signieren und signieren Sie die Assembly mit einem neuen Schlüssel. Deaktivieren Sie die Kennwortanforderung und verwenden Sie einen beliebigen Schlüsseldateinamen.
  • Fügen Sie den folgenden Code zum Ersetzen ein Program.cs. Dies ist eine abgekürzte Version des Codes in diesem Microsoft-Beispiel . Ich habe alle Pfade gleich gehalten. Wenn Sie also zum vollständigeren Code zurückkehren möchten, sollten Sie nichts anderes ändern müssen.

Code:

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);
    }  
}
  • Erstellen Sie ein weiteres Projekt mit dem Namen "UntrustedCode". Dies sollte ein klassisches Desktop Class Library-Projekt sein.
  • Unterschreiben Sie die Versammlung; Sie können einen neuen oder denselben Schlüssel wie für CodeRunner verwenden. (Dies dient zum Teil dazu, die Noda Time-Situation nachzuahmen, und zum Teil, um die Code-Analyse bei Laune zu halten.)
  • Fügen Sie den folgenden Code ein Class1.cs(überschreiben Sie, was vorhanden ist):

Code:

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

Das Ausführen des CodeRunner-Projekts führt zu der folgenden Ausnahme (aus Gründen der Lesbarkeit neu formatiert):

Nicht behandelte Ausnahme: System.Reflection.TargetInvocationException: Die
Ausnahme wurde vom Ziel eines Aufrufs ausgelöst.
--->
System.TypeLoadException:
Vererbungssicherheitsregeln beim Überschreiben des Mitglieds verletzt:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
Die Sicherheitszugänglichkeit der überschreibenden Methode muss mit der Sicherheitszugänglichkeit der überschriebenen Methode übereinstimmen
.

Die auskommentierten Attribute zeigen Dinge, die ich versucht habe:

  • SecurityPermissionwird von zwei verschiedenen MS-Artikeln empfohlen ( erster , zweiter ), obwohl sie interessanterweise verschiedene Dinge in Bezug auf die explizite / implizite Schnittstellenimplementierung tun
  • SecurityCriticalist das, was Noda Time derzeit hat und was die Antwort dieser Frage nahelegt
  • SecuritySafeCritical wird durch Code Analysis-Regelmeldungen etwas vorgeschlagen
  • Ohne irgendwelche Attribute sind Code - Analyse - Regeln glücklich - entweder mit SecurityPermissionoder SecurityCritical Gegenwart, die Regeln , die Sie sagen , die Attribute entfernen - es sei denn , Sie tun haben AllowPartiallyTrustedCallers. In beiden Fällen hilft es nicht, den Vorschlägen zu folgen.
  • Noda Time hat sich darauf AllowPartiallyTrustedCallersbeworben; Das Beispiel hier funktioniert weder mit noch ohne das angewendete Attribut.

Der Code wird ausnahmslos ausgeführt, wenn ich [assembly: SecurityRules(SecurityRuleSet.Level1)]der UntrustedCodeAssembly etwas hinzufüge (und das AllowPartiallyTrustedCallersAttribut auskommentiere ), aber ich glaube, dass dies eine schlechte Lösung für das Problem ist, das anderen Code behindern könnte.

Ich gebe voll und ganz zu, dass ich ziemlich verloren bin, wenn es um diese Art von Sicherheitsaspekt von .NET geht. Was kann ich also tun, um auf .NET 4.5 abzuzielen und dennoch zuzulassen, dass meine Typen ISerializablein Umgebungen wie .NET Fiddle implementiert werden und weiterhin verwendet werden?

(Während ich auf .NET 4.5 abziele, sind es meiner Meinung nach die Änderungen der .NET 4.0-Sicherheitsrichtlinien, die das Problem verursacht haben, daher das Tag.)

Jon Skeet
quelle
Interessanterweise legt diese Erklärung der Änderungen am Sicherheitsmodell in 4.0 nahe, dass das einfache Entfernen AllowPartiallyTrustedCallersden Trick machen sollte, aber es scheint keinen Unterschied zu machen
Mathias R. Jessen

Antworten:

56

Laut MSDN sollten Sie in .NET 4.0 grundsätzlich keinen ISerializableteilweise vertrauenswürdigen Code verwenden, sondern ISafeSerializationData

Zitieren von https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization

Wichtig

In Versionen vor .NET Framework 4.0 wurde die Serialisierung von benutzerdefinierten Benutzerdaten in einer teilweise vertrauenswürdigen Assembly mithilfe von GetObjectData durchgeführt. Ab Version 4.0 ist diese Methode mit dem SecurityCriticalAttribute-Attribut gekennzeichnet, das die Ausführung in teilweise vertrauenswürdigen Assemblys verhindert. Implementieren Sie die ISafeSerializationData-Schnittstelle, um diese Bedingung zu umgehen.

Also wahrscheinlich nicht das, was Sie hören wollten, wenn Sie es brauchen, aber ich glaube, es gibt keinen Weg daran vorbei, während Sie es weiter verwenden ISerializable(außer zur Level1Sicherheit zurückzukehren, von der Sie sagten, dass Sie es nicht wollen).

PS: In den ISafeSerializationDataDokumenten wird angegeben, dass es sich nur um Ausnahmen handelt, aber es scheint nicht so spezifisch zu sein. Vielleicht möchten Sie es ausprobieren ... Ich kann es im Grunde nicht mit Ihrem Beispielcode testen (außer das Entfernen von ISerializableWerken, aber das wusstest du schon) ... du musst sehen, ob es ISafeSerializationDatadir genug passt.

PS2: Das SecurityCriticalAttribut funktioniert nicht, da es ignoriert wird, wenn die Assembly im Teilvertrauensmodus geladen wird ( auf Level2-Sicherheit ). Sie können es auf Ihrem Beispielcode sehen, wenn Sie den Debug - targetVariable in der ExecuteUntrustedCoderechten , bevor es aufgerufen wird , wird es hat IsSecurityTransparentauf trueund IsSecurityCriticalzu , falseauch wenn Sie die Methode mit dem markieren SecurityCriticalAttribute)

Jcl
quelle
Aha - danke für die Erklärung. Schade, dass die Ausnahme hier so irreführend ist. Muss herausfinden, was zu tun ist ...
Jon Skeet
@ JonSkeet Ehrlich gesagt, ich würde die binäre Serialisierung insgesamt aufgeben ... aber ich verstehe, dass Ihre Benutzerbasis es vielleicht nicht mag
Jcl
Ich denke, wir müssen das tun - was bedeutet, auf v3.0 umzusteigen. Es hat aber noch andere Vorteile ... Ich muss die Noda Time Community konsultieren.
Jon Skeet
12
@ JonSkeet übrigens, wenn Sie interessiert sind, erklärt dieser Artikel die Unterschiede zwischen Level 1 und Level 2 Sicherheit (und warum es nicht funktioniert)
Jcl
8

Die akzeptierte Antwort ist so überzeugend, dass ich fast geglaubt hätte, dies sei kein Fehler. Aber nachdem ich jetzt einige Experimente durchgeführt habe, kann ich sagen, dass Level2-Sicherheit ein komplettes Durcheinander ist. Zumindest ist etwas wirklich faul.

Vor ein paar Tagen bin ich mit meinen Bibliotheken auf dasselbe Problem gestoßen. Ich habe schnell einen Unit-Test erstellt. Ich konnte jedoch das in .NET Fiddle aufgetretene Problem nicht reproduzieren, während derselbe Code die Ausnahme in einer Konsolen-App "erfolgreich" auslöste. Am Ende habe ich zwei seltsame Wege gefunden, um das Problem zu lösen.

TL; DR : Wenn Sie in Ihrem Consumer-Projekt einen internen Typ der verwendeten Bibliothek verwenden, funktioniert der teilweise vertrauenswürdige Code wie erwartet: Er kann eine ISerializableImplementierung instanziieren (und ein sicherheitskritischer Code kann nicht direkt aufgerufen werden.) aber siehe unten). Oder, was noch lächerlicher ist, Sie können versuchen, die Sandbox erneut zu erstellen, wenn sie zum ersten Mal nicht funktioniert hat ...

Aber sehen wir uns einen Code an.

ClassLibrary.dll:

Lassen Sie uns zwei Fälle trennen: einen für eine reguläre Klasse mit sicherheitskritischen Inhalten und einen für die ISerializableImplementierung:

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

Eine Möglichkeit, das Problem zu beheben, besteht darin, einen internen Typ aus der Consumer-Assembly zu verwenden. Jeder Typ wird es tun; Jetzt definiere ich ein Attribut:

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

Und die relevanten Attribute, die auf die Baugruppe angewendet werden:

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

Signieren Sie die Assembly, wenden Sie den Schlüssel auf das InternalsVisibleToAttribut an und bereiten Sie sich auf das Testprojekt vor:

UnitTest.dll (verwendet NUnit und ClassLibrary):

Um den internen Trick anzuwenden, sollte auch die Testbaugruppe signiert werden. Baugruppenattribute:

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

Hinweis : Das Attribut kann überall angewendet werden. In meinem Fall war es eine Methode in einer zufälligen Testklasse, für die ich ein paar Tage gebraucht habe.

Hinweis 2 : Wenn Sie alle Testmethoden zusammen ausführen, kann es vorkommen, dass die Tests bestanden werden.

Das Skelett der Testklasse:

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

Und schauen wir uns die Testfälle einzeln an

Fall 1: ISerialisierbare Implementierung

Das gleiche Problem wie in der Frage. Der Test besteht wenn

  • InternalTypeReferenceAttribute wird angewandt
  • Es wird mehrmals versucht, eine Sandbox zu erstellen (siehe Code).
  • oder wenn alle Testfälle gleichzeitig ausgeführt werden und dies nicht der erste ist

Andernfalls tritt Inheritance security rules violated while overriding member...beim Instanziieren die völlig unangemessene Ausnahme auf 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);
    }

}

Fall 2: Regelmäßiger Unterricht mit sicherheitskritischen Mitgliedern

Der Test besteht unter den gleichen Bedingungen wie der erste. Das Problem ist hier jedoch völlig anders: Ein teilweise vertrauenswürdiger Code kann direkt auf ein sicherheitskritisches Mitglied zugreifen .

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

Fall 3-4: Vollvertrauensversionen von Fall 1-2

Der Vollständigkeit halber werden hier dieselben Fälle wie oben in einer vollständig vertrauenswürdigen Domäne ausgeführt. Wenn Sie [assembly: AllowPartiallyTrustedCallers]die fehlgeschlagenen Tests entfernen , können Sie direkt auf kritischen Code zugreifen (da die Methoden standardmäßig nicht mehr transparent sind).

[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:

Dies wird Ihr Problem mit .NET Fiddle natürlich nicht lösen. Aber jetzt wäre ich sehr überrascht, wenn es kein Fehler im Framework wäre.

Die größte Frage für mich ist jetzt der zitierte Teil in der akzeptierten Antwort. Wie sind sie mit diesem Unsinn herausgekommen? Das ISafeSerializationDataist eindeutig keine Lösung für irgendetwas: Es wird ausschließlich von der Basisklasse verwendet Exceptionund wenn Sie das SerializeObjectStateEreignis abonnieren (warum ist das keine überschreibbare Methode?), Wird der Status am Ende auch von der konsumiert Exception.GetObjectData.

Das AllowPartiallyTrustedCallers/ SecurityCritical/ SecuritySafeCriticalTriumvirat der Attribute wurde genau für die oben gezeigte Verwendung entwickelt. Es scheint mir totaler Unsinn, dass ein teilweise vertrauenswürdiger Code einen Typ nicht einmal instanziieren kann, unabhängig davon, ob versucht wird, seine sicherheitskritischen Mitglieder zu verwenden. Es ist jedoch ein noch größerer Unsinn ( eigentlich eine Sicherheitslücke ), dass ein teilweise vertrauenswürdiger Code direkt auf eine sicherheitskritische Methode zugreifen kann (siehe Fall 2 ), während dies für transparente Methoden selbst aus einer vollständig vertrauenswürdigen Domäne verboten ist.

Wenn es sich bei Ihrem Verbraucherprojekt also um einen Test oder eine andere bekannte Baugruppe handelt, kann der interne Trick perfekt angewendet werden. Für .NET Fiddle und andere reale Sandbox-Umgebungen ist die einzige Lösung, auf die zurückgesetzt wird, SecurityRuleSet.Level1bis dies von Microsoft behoben wurde.


Update: Für das Problem wurde ein Developer Community-Ticket erstellt.

György Kőszeg
quelle
2

Laut MSDN siehe:

Wie behebe ich Verstöße?

Um einen Verstoß gegen diese Regel zu beheben, machen Sie die GetObjectData- Methode sichtbar und überschreibbar und stellen Sie sicher, dass alle Instanzfelder im Serialisierungsprozess enthalten sind oder explizit mit dem NonSerializedAttribute- Attribut gekennzeichnet sind.

Das folgende Beispiel behebt die beiden vorherigen Verstöße, indem eine überschreibbare Implementierung von ISerializable.GetObjectData für die Book-Klasse und eine Implementierung von ISerializable.GetObjectData für die Library-Klasse bereitgestellt wird.

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
quelle
2
Der Artikel, auf den Sie verlinkt haben, ist für CA2240, der nicht ausgelöst wird - der Code verletzt ihn nicht. Es ist eine Struktur, also effektiv versiegelt; es hat keine Felder; es wird GetObjectDataexplizit implementiert , aber dies hilft implizit nicht.
Jon Skeet
15
Sicher, und danke, dass Sie es versucht haben - aber ich erkläre, warum es nicht funktioniert. (Und als Empfehlung - für etwas Kniffliges wie dieses, bei dem die Frage ein überprüfbares Beispiel enthält, ist es eine gute Idee, zu versuchen, das vorgeschlagene Update anzuwenden und zu prüfen, ob es tatsächlich hilft.)
Jon Skeet