Jak mogę wdrożyć ISerializable w .NET 4+ bez naruszania reguł bezpieczeństwa dziedziczenia?

110

Tło: Noda Time zawiera wiele struktur możliwych do serializacji. Chociaż nie lubię serializacji binarnej, otrzymaliśmy wiele próśb o jej obsługę, już na osi czasu 1.x. Wspieramy to implementując ISerializableinterfejs.

Otrzymaliśmy najnowsze zgłoszenie problemu z awarią Noda Time 2.x w .NET Fiddle . Ten sam kod używający Noda Time 1.x działa dobrze. Zgłoszony wyjątek jest następujący:

Naruszenie reguł bezpieczeństwa dziedziczenia podczas zastępowania elementu członkowskiego: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Dostępność zabezpieczeń metody zastępującej musi odpowiadać dostępności zabezpieczeń zastępowanej metody.

Zawęziłem to do struktury docelowej: 1.x jest przeznaczony dla .NET 3.5 (profil klienta); 2.x jest przeznaczony dla platformy .NET 4.5. Mają duże różnice pod względem obsługi PCL vs .NET Core i struktury plików projektu, ale wygląda na to, że nie ma to znaczenia.

Udało mi się to odtworzyć w lokalnym projekcie, ale nie znalazłem rozwiązania.

Kroki do odtworzenia w VS2017:

  • Utwórz nowe rozwiązanie
  • Utwórz nową klasyczną aplikację konsoli systemu Windows przeznaczoną dla platformy .NET 4.5.1. Nazwałem to „CodeRunner”.
  • We właściwościach projektu przejdź do podpisywania i podpisz zestaw nowym kluczem. Odznacz wymaganie dotyczące hasła i użyj dowolnej nazwy pliku klucza.
  • Wklej następujący kod, aby zamienić Program.cs. To jest skrócona wersja kodu w tym przykładzie firmy Microsoft . Zachowałem wszystkie ścieżki bez zmian, więc jeśli chcesz wrócić do pełniejszego kodu, nie powinieneś zmieniać niczego więcej.

Kod:

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);
    }  
}
  • Utwórz inny projekt o nazwie „UntrustedCode”. Powinien to być projekt biblioteki klas klasycznego pulpitu.
  • Podpisz zgromadzenie; możesz użyć nowego klucza lub tego samego, co w CodeRunner. (Ma to częściowo naśladować sytuację czasu Noda, a częściowo zapewnić zadowolenie analizy kodu).
  • Wklej następujący kod Class1.cs(nadpisując to, co tam jest):

Kod:

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

Uruchomienie projektu CodeRunner daje następujący wyjątek (sformatowany w celu zwiększenia czytelności):

Nieobsługiwany wyjątek: System.Reflection.TargetInvocationException: obiekt
docelowy wywołania zgłosił wyjątek.
--->
System.TypeLoadException:
dziedziczenie reguł bezpieczeństwa naruszone podczas zastępowania elementu członkowskiego:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
Dostępność zabezpieczeń metody zastępującej musi odpowiadać
dostępności zabezpieczeń zastępowanej metody.

Skomentowane atrybuty pokazują rzeczy, których próbowałem:

  • SecurityPermissionjest zalecany przez dwa różne artykuły MS ( pierwszy , drugi ), chociaż, co ciekawe, robią one różne rzeczy wokół jawnej / niejawnej implementacji interfejsu
  • SecurityCriticaljest tym, czym obecnie dysponuje Noda Time i co sugeruje odpowiedź na to pytanie
  • SecuritySafeCritical jest nieco sugerowane przez komunikaty reguł analizy kodu
  • Bez żadnych atrybutów, reguły Code Analysis są zadowoleni - z albo SecurityPermissionczy SecurityCritical obecne przepisy trzeba by usunąć atrybuty - chyba, że zrobić mają AllowPartiallyTrustedCallers. Postępowanie zgodnie z sugestiami w obu przypadkach nie pomaga.
  • Noda Time się do tego AllowPartiallyTrustedCallerszastosował; przykład tutaj nie działa ani z zastosowanym atrybutem, ani bez niego.

Kod działa bez wyjątku, jeśli dodam [assembly: SecurityRules(SecurityRuleSet.Level1)]do UntrustedCodezestawu (i odkomentuję plikAllowPartiallyTrustedCallers atrybut), ale uważam, że jest to słabe rozwiązanie problemu, który może utrudniać inny kod.

W pełni przyznaję, że jestem dość zagubiony, jeśli chodzi o tego rodzaju aspekt bezpieczeństwa .NET. Więc co można zrobić, aby kierować .NET 4.5 i pozwalają jeszcze moje typy do wdrożenia ISerializablei nadal być stosowane w środowiskach takich jak .NET Fiddle?

(Chociaż moim celem jest .NET 4.5, uważam, że przyczyną problemu są zmiany zasad bezpieczeństwa .NET 4.0, stąd znacznik).

Jon Skeet
źródło
Co ciekawe, to wyjaśnienie zmian w modelu zabezpieczeń w 4,0 sugeruje, że po prostu usunięcie AllowPartiallyTrustedCallerspowinno załatwić sprawę, ale nie wydaje się, aby różnica
Mathias R. Jessen

Odpowiedzi:

56

Według MSDN w .NET 4.0 w zasadzie nie należy używać ISerializablekodu częściowo zaufanego, a zamiast tego należy używać ISafeSerializationData

Cytowanie z https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization

Ważny

W wersjach wcześniejszych niż .NET Framework 4.0 serializacja niestandardowych danych użytkownika w częściowo zaufanym zestawie była wykonywana przy użyciu metody GetObjectData. Począwszy od wersji 4,0, ta metoda jest oznaczona atrybutem SecurityCriticalAttribute, który zapobiega wykonywaniu w częściowo zaufanych zestawach. Aby obejść ten warunek, zaimplementuj interfejs ISafeSerializationData.

Więc prawdopodobnie nie to, co chciałeś usłyszeć, jeśli tego potrzebujesz, ale nie sądzę, aby można było to obejść podczas dalszego używania ISerializable(poza powrotem doLevel1 ochrony, której powiedziałeś, że nie chcesz).

PS: ISafeSerializationDatadokumentacja stwierdza, że ​​dotyczy to tylko wyjątków, ale nie wydaje się to zbyt szczegółowe, możesz spróbować ... Zasadniczo nie mogę tego przetestować z twoim przykładowym kodem (poza usuwaniem ISerializableprac, ale już to wiedziałeś) ... będziesz musiał zobaczyć, czyISafeSerializationData ci odpowiada.

PS2: SecurityCriticalatrybut nie działa, ponieważ jest ignorowany, gdy zestaw jest ładowany w trybie częściowego zaufania ( w przypadku zabezpieczeń poziomu 2 ). Widać to na przykładowy kod, jeśli debugowanie targetzmiennej w ExecuteUntrustedCodeprawo przed wywołaniem go, to będzie mieć IsSecurityTransparentdo truei IsSecurityCriticaldo falsenawet jeśli oznaczyć metodę z SecurityCriticalatrybutem)

Jcl
źródło
Aha - dzięki za wyjaśnienie. Szkoda, że ​​wyjątek jest tutaj tak mylący. Będę musiał wymyślić, co robić ...
Jon Skeet
@JonSkeet Szczerze, zrezygnowałbym całkowicie z serializacji binarnej ... ale rozumiem, że twojej
bazie
Myślę, że będziemy musieli to zrobić - co oznacza przejście do wersji 3.0. Ma to jednak inne zalety ... Muszę skonsultować się ze społecznością Noda Time.
Jon Skeet,
12
@JonSkeet przy okazji, jeśli jesteś zainteresowany, ten artykuł wyjaśnia różnice między zabezpieczeniami na poziomie 1 i 2 (i DLACZEGO to nie działa)
Jcl,
8

Przyjęta odpowiedź jest tak przekonująca, że ​​prawie uwierzyłem, że to nie był błąd. Ale po przeprowadzeniu kilku eksperymentów mogę teraz powiedzieć, że bezpieczeństwo Level2 to kompletny bałagan; przynajmniej coś jest naprawdę podejrzane.

Kilka dni temu wpadłem na ten sam problem z moimi bibliotekami. Szybko stworzyłem test jednostkowy; nie udało mi się jednak odtworzyć problemu, którego doświadczyłem w .NET Fiddle, podczas gdy ten sam kod „pomyślnie” wyrzucił wyjątek w aplikacji konsolowej. W końcu znalazłem dwa dziwne sposoby rozwiązania problemu.

TL; DR : Okazuje się, że jeśli używasz wewnętrznego typu używanej biblioteki w swoim projekcie konsumenckim, to częściowo zaufany kod działa zgodnie z oczekiwaniami: jest w stanie zainicjować ISerializableimplementację (a krytycznego dla bezpieczeństwa kodu nie można wywołać bezpośrednio, ale patrz poniżej). Lub, co jest jeszcze bardziej śmieszne, możesz spróbować ponownie utworzyć piaskownicę, jeśli nie zadziałała po raz pierwszy ...

Ale zobaczmy trochę kodu.

ClassLibrary.dll:

Oddzielmy dwa przypadki: jeden dla zwykłej klasy z treścią krytyczną dla bezpieczeństwa i jeden ISerializableimplementacyjny:

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

Jednym ze sposobów rozwiązania tego problemu jest użycie typu wewnętrznego z zespołu konsumenckiego. Każdy typ to zrobi; teraz definiuję atrybut:

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

Oraz odpowiednie atrybuty zastosowane do zespołu:

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

Podpisz zestaw, zastosuj klucz do pliku InternalsVisibleTo atrybutu i przygotuj się na projekt testowy:

UnitTest.dll (używa NUnit i ClassLibrary):

Aby skorzystać z wewnętrznej sztuczki, należy również podpisać testowy montaż. Atrybuty zespołu:

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

Uwaga : atrybut można zastosować w dowolnym miejscu. W moim przypadku była to metoda z losowej klasy testowej, której znalezienie zajęło mi kilka dni.

Uwaga 2 : Jeśli uruchomisz wszystkie metody testowe razem, może się zdarzyć, że testy przejdą pomyślnie.

Szkielet klasy testowej:

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

Zobaczmy kolejno przypadki testowe

Przypadek 1: implementacja ISerializowalna

Ta sama kwestia, co w pytaniu. Test przechodzi, jeśli

  • InternalTypeReferenceAttribute jest stosowany
  • próbowano utworzyć piaskownicę wiele razy (patrz kod)
  • lub, jeśli wszystkie przypadki testowe są wykonywane jednocześnie, a to nie jest pierwszy

W przeciwnym razie Inheritance security rules violated while overriding member...podczas tworzenia instancji pojawi się całkowicie nieodpowiedni wyjątek 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);
    }

}

Przypadek 2: Zwykła klasa z elementami kluczowymi dla bezpieczeństwa

Test przechodzi na takich samych warunkach jak pierwszy. Jednak problem jest tutaj zupełnie inny: częściowo zaufany kod może uzyskać bezpośredni dostęp do członka krytycznego dla bezpieczeństwa .

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

Przypadek 3-4: Wersje przypadku 1-2 z pełnym zaufaniem

W trosce o kompletność tutaj są te same przypadki, co powyższe, wykonane w w pełni zaufanej domenie. Jeśli usuniesz [assembly: AllowPartiallyTrustedCallers]testy zakończą się niepowodzeniem, ponieważ możesz uzyskać bezpośredni dostęp do krytycznego kodu (ponieważ metody nie są już domyślnie przezroczyste).

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

Oczywiście nie rozwiąże to Twojego problemu z .NET Fiddle. Ale teraz byłbym bardzo zaskoczony, gdyby to nie był błąd we frameworku.

Największym pytaniem do mnie jest teraz cytowana część zaakceptowanej odpowiedzi. Jak wyszli z tym nonsensem? ISafeSerializationDataWyraźnie nie jest rozwiązaniem na wszystko: jest używany wyłącznie przez bazowej Exceptionklasy i jeśli subskrybować SerializeObjectStatezdarzenie (dlaczego nie jest to, że metoda przeciążać?), To państwo będzie również być spożywane przezException.GetObjectData w końcu.

AllowPartiallyTrustedCallers/ SecurityCritical/ SecuritySafeCriticalTriumwirat atrybutów zostały zaprojektowane dokładnie wykorzystaniem przedstawionego powyżej. Wydaje mi się kompletnym nonsensem, że częściowo zaufany kod nie może nawet utworzyć wystąpienia typu, niezależnie od próby użycia jego elementów krytycznych dla bezpieczeństwa. Ale jeszcze większym nonsensem (a właściwie luka w zabezpieczeniach ) jest to, że częściowo zaufany kod może uzyskać bezpośredni dostęp do metody krytycznej dla bezpieczeństwa (patrz przypadek 2 ), podczas gdy jest to zabronione w przypadku metod przezroczystych, nawet z w pełni zaufanej domeny.

Więc jeśli twój projekt konsumencki to test lub inny dobrze znany montaż, wtedy wewnętrzna sztuczka może być doskonale wykorzystana. W przypadku .NET Fiddle i innych rzeczywistych środowisk piaskownicy jedynym rozwiązaniem jest powrót do SecurityRuleSet.Level1czasu, gdy zostanie to naprawione przez firmę Microsoft.


Aktualizacja: bilet Społeczność Twórca został stworzony dla tej kwestii.

György Kőszeg
źródło
2

Według MSDN patrz:

Jak naprawić naruszenia?

Aby naprawić naruszenie tej reguły, ustaw metodę GetObjectData widoczną i możliwą do zastąpienia oraz upewnij się, że wszystkie pola wystąpienia są uwzględnione w procesie serializacji lub jawnie oznaczone atrybutem NonSerializedAttribute .

Poniższy przykład rozwiązuje dwa poprzednie naruszenia, dostarczając możliwą do zastąpienia implementację ISerializable.GetObjectData w klasie Book i udostępniając implementację ISerializable.GetObjectData w klasie Library.

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
źródło
2
Artykuł, do którego prowadzi link, dotyczy CA2240, który nie został uruchomiony - kod go nie narusza. To struktura, więc jest skutecznie zapieczętowana; nie ma żadnych pól; implementuje GetObjectDatajawnie, ale robienie tego niejawnie nie pomaga.
Jon Skeet
15
Jasne i dziękuję za próbę - ale wyjaśniam, dlaczego to nie działa. (I jak zalecenia - czegoś tak trudne, gdzie kwestia obejmuje zweryfikowania przykład, że to dobry pomysł, aby spróbować zastosować sugerowaną poprawkę i sprawdzić, czy to rzeczywiście pomaga.)
Jon Skeet