Jak mogę wdrożyć ISerializable in.NET 4+ bez naruszania zasad bezpieczeństwa spadkowego?

Background: Noda Time zawiera wiele serializowalne struktury. Chociaż nie lubię serializacji binarnej, my otrzymał wiele próśb o jego poparcie, jeszcze w 1.x timeline. Wspieramy go poprzez implementację interfejsu ISerializable.

Otrzymaliśmy ostatni numer raport Noda Czas 2.x awaria w. NET Fiddle . Ten sam kod za pomocą Noda Czas 1.x działa dobrze. Wyjątek rzucony jest taki:

Zasady bezpieczeństwa dziedziczenia naruszone podczas członek nadrzędny: NodaTime.Czas trwania.System.Runtime.Serializacja.ISerializable.GetObjectData (System.Runtime.Serializacja.SerializationInfo, System.Runtime.Serializacja.StreamingContext)". Bezpieczeństwo dostępność nadrzędnej metody musi być zgodna z zabezpieczeniem dostępność nadpisanej metody.

Zawęziłem to do frameworka, który jest skierowany: 1.x cele. NET 3.5 (profil klienta); 2.x cele. NET 4.5. Mają duże różnice pod względem wsparcie PCL vs. Net Core i struktura plików projektu, ale wygląda na to, że jest to nieistotne.

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

Kroki do odtworzenia w VS2017:

  • Utwórz nowe rozwiązanie
  • Utwórz nową klasyczną aplikację na konsolę Windows skierowaną do. NET 4.5.1. Nazwałem go "CodeRunner".
  • we właściwościach projektu przejdź do zakładki podpisywanie i podpisywanie złożenia za pomocą nowy klucz. Odkleić wymagania dotyczące hasła i użyj dowolnej nazwy pliku klucza.
  • wklej poniższy kod, aby zastąpić Program.cs. To jest skrócona wersja kodu w tego Microsoft próbka . Wszystkie ścieżki są takie same, więc jeśli chcesz wrócić do pełniejszy kod, nie powinieneś zmieniać niczego innego.

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 kolejny projekt o nazwie "UntrustedCode". To powinno być Klasyczny projekt biblioteki klas pulpitu.
  • podpisz Zgromadzenie; ty można użyć nowego klucza lub tego samego, co dla Programista. (Ma to częściowo naśladować sytuację czasu Noda, i po części, aby analiza kodu była zadowolona.)
  • wklej poniższy kod w Class1.cs (nadpisując 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 pod kątem czytelności):

Nieobsługiwany Wyjątek: System.Odbicie.TargetInvocationException:
Wyjątek został wyrzucony przez cel inwokacja.
--->
System.TypeLoadException:
Zasady bezpieczeństwa dziedziczenia naruszone podczas nadrzędnego członka:
"UntrustedCode.CustomStruct.System.Runtime.Serializacja.ISerializable.GetObjectData(...).
Dostępność zabezpieczeń metody nadrzędnej musi być zgodna z zabezpieczeniem
dostępność nadpisanej metody.

Komentowane atrybuty pokazują rzeczy, które próbowałem:

  • SecurityPermission jest zalecany przez dwa różne państwa członkowskie artykuły (pierwsze , druga ), chociaż co ciekawe, robią różne rzeczy wokół jawnej / niejawnej implementacji interfejsu
  • SecurityCritical jest tym, co Obecnie ma czas Noda i jest tym, co sugeruje odpowiedź na to pytanie [38]}
  • SecuritySafeCritical jest nieco sugerowane przez komunikaty reguł analizy kodu
  • bez żadnych atrybutów, reguły analizy kodu są szczęśliwe-z SecurityPermission lub SecurityCritical obecnych, Zasady nakazują usunięcie atrybutów-chyba że ty czy masz AllowPartiallyTrustedCallers. Podążanie za sugestiami w obu przypadkach nie pomaga.
  • czas Noda zastosował AllowPartiallyTrustedCallers; przykład tutaj nie działa ani z atrybutem, ani bez niego.

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

W pełni przyznaję się do bycia całkiem zagubionym, jeśli chodzi o to tak jakby. aspekt bezpieczeństwa .NET. więc co może zrobić, aby cel. NET 4.5 i jeszcze pozwolić moim typom zaimplementować ISerializable i nadal być używane w środowiska takie jak. NET?

(podczas gdy celuję w. NET 4.5, wierzę, że to zmiany polityki bezpieczeństwa. NET 4.0 spowodowały problem, stąd tag.)

Author: Jon Skeet, 2018-01-20

2 answers

W. NET 4.0 zasadniczo nie powinieneś używać ISerializable Dla częściowo zaufanego kodu, a zamiast tego powinieneś używać ISafeSerializationData

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

Ważne

W wersjach wcześniejszych niż. NET Framework 4.0 serializacja niestandardowych danych użytkownika w częściowo zaufanym zestawie została wykonana przy użyciu GetObjectData. Począwszy od wersji 4.0, metoda ta jest oznaczona atrybutem SecurityCriticalAttribute, który uniemożliwia wykonywanie w częściowo zaufanych złożeniach. 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 było możliwe obejście go podczas używania ISerializable (inne niż powrót do Level1 zabezpieczeń, które mówiłeś, że nie chcesz).

PS: the ISafeSerializationData dokumenty stwierdzają, że jest to tylko dla wyjątków, ale nie wydaje się to tak szczegółowe, możesz spróbować... W zasadzie nie mogę go przetestować za pomocą przykładowego kodu (poza usunięciem ISerializable działa, ale już o tym wiedziałeś)... musisz sprawdzić, czy ci pasuje.

PS2: atrybut SecurityCritical nie działa, ponieważ jest ignorowany, gdy zespół jest ładowany w trybie częściowego zaufania (w zabezpieczeniach Level2 ). Możesz go zobaczyć na przykładowym kodzie, jeśli debugujesz zmienną target w ExecuteUntrustedCode tuż przed wywołaniem, będzie ona miała IsSecurityTransparent do true i IsSecurityCritical do false, nawet jeśli zaznaczysz metodę atrybutem SecurityCritical)

 31
Author: Jcl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-20 19:42:30

Zgodnie z MSDN patrz:

Jak naprawić naruszenia?

Aby naprawić naruszenie tej reguły, upewnij się, że metoda GetObjectData jest widoczna i nadpisywalna oraz upewnij się, że wszystkie pola instancji są zawarte w procesie serializacji lub wyraźnie oznaczone atrybutem NonSerializedAttribute .

Poniższy przykład naprawia dwa poprzednie naruszenia, zapewniając nadmiarową implementację ISerializable.GetObjectData na klasie Book I poprzez dostarczenie implementacji ISerializable.GetObjectData na klasie Biblioteki.

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);
        }
    }
}
 1
Author: user5377037,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-20 13:03:36