Как я могу реализовать ISerializable in.NET 4+ без нарушения правил безопасности наследования?


Справочная информация: Нода Времени содержит много сериализуемые структуры. Хотя мне не нравится двоичная сериализация, мы получил много запросов, чтобы поддержать его, еще в 1.х сроков. Мы поддерживаем его, реализуя ISerializable интерфейс.

мы получили недавно вопрос доклад Нода Время 2.x не в пределах .Чистая Скрипка. Один и тот же код с помощью Нода Время 1.X работает нормально. Исключение создается следующим образом:

наследование нарушены правила безопасности при переопределении член: - Нодатим.Продолжительность.Система.Во время выполнения.Сериализация.Интерфейс ISerializable.GetObjectData (System.Во время выполнения.Сериализация.SerializationInfo, Система.Во время выполнения.Сериализация.StreamingContext)'. Безопасность доступность переопределяющего метода должна соответствовать безопасности доступность переопределяемого метода.

я сузил это до рамок, которые предназначены: 1.икс целевые объекты .NET 3.5 (профиль клиента); 2.икс цели .NET 4.5. У них есть большие различия с точки зрения поддержки PCL vs .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(@"......UntrustedCodebinDebug");  
        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". Это должно быть Классический проект библиотеки классов рабочего стола.
  • подписать сборку; вы можете использовать новый ключ или тот же, что и для CodeRunner. (Это частично имитирует ситуацию времени Noda, и отчасти, чтобы сохранить анализ кода счастливым.)
  • вставьте следующий код в 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 дает следующее исключение (переформатировано для удобства чтения):

Необработанное Исключение: Система.Отображение.TargetInvocationException:
Исключение вызвано адресат вызова.
--- >
Система.TypeLoadException:
Наследование нарушены правила безопасности при переопределении член:
- Недоверенный код.CustomStruct.Система.Во время выполнения.Сериализация.Интерфейс ISerializable.GetObjectData(...).
Доступность безопасности переопределяющего метода должна соответствовать безопасность
доступность переопределяемого метода.

закомментированные атрибуты показывают вещи, которые я пробовал:

  • SecurityPermission рекомендуется двумя различными статьями MS (первый, второй), хотя интересно, что они делают разные вещи вокруг явной / неявной реализации интерфейса
  • SecurityCritical это то, что нода время в настоящее время имеет, и это то, что ответ на этот вопрос предлагает
  • SecuritySafeCritical несколько подсказывает правило анализа кода messages
  • без любой атрибуты, правила анализа кода довольны - с SecurityPermission или SecurityCritical представьте, правила говорят вам удалить атрибуты-если вы do есть AllowPartiallyTrustedCallers. Следующие предложения в любом случае не поможет.
  • Нода времени AllowPartiallyTrustedCallers применяется к нему; пример здесь не работает ни с атрибутом, ни без него прикладная.

код работает без исключения, если я добавлю [assembly: SecurityRules(SecurityRuleSet.Level1)] до UntrustedCode сборка (и раскомментируйте AllowPartiallyTrustedCallers атрибут), но я считаю, что это плохое решение проблемы, которая может помешать другому коду.

я полностью признаю, что довольно потерян, когда дело доходит до такого рода аспект безопасности .NET. So what можете я делаю для целевой .NET 4.5 и но позвольте моим типам реализовать ISerializable и все еще используйте внутри такие среды, как .NET Скрипка?

(в то время как я нацелен на .NET 4.5, я считаю, что это изменения политики безопасности .NET 4.0, которые вызвали проблему, следовательно, тег.)

2 70

2 ответа:

по данным в разделе, в .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 безопасность, которую вы сказал, что не хочешь).

ЗЫ:ISafeSerializationData документы утверждают, что это только для исключений, но это не кажется все, что конкретно, вы можете дать ему шанс... Я в принципе не могу проверить его с вашим примером кода (кроме удаления ISerializable работает, но вы это уже знали)... вы должны будете увидеть, если ISafeSerializationData достаточно подходит вам.

PS2: the SecurityCritical атрибут не работает, потому что он игнорируется при загрузке сборки в режиме частичного доверия ( на уровне 2 безопасность). Вы можете увидеть это в своем примере кода, если вы отлаживаете target переменная ExecuteUntrustedCode прямо перед вызовом он будет иметь IsSecurityTransparent до true и IsSecurityCritical до false даже если вы отмечаете метод с )

по словам MSDN посмотреть:

как исправить нарушения?

чтобы исправить нарушение этого правила, Сделайте GetObjectData метод видимый и переопределяемый и убедитесь, что все поля экземпляра включены в процесс сериализации или явно помечены атрибутом nonserializedattribute.

следующее пример исправляет два предыдущих нарушения путем обеспечение переопределяемой реализации ISerializable.GetObjectData в классе Book и путем предоставления реализации 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);
        }
    }
}