Предпосылки: Noda Time содержит множество сериализуемых структур. Хотя мне не нравится двоичная сериализация, мы получили много запросов на ее поддержку еще на временной шкале 1.x. Мы поддерживаем это реализацией ISerializable
интерфейса.
Мы получили недавний отчет о сбое Noda Time 2.x в .NET Fiddle . Тот же код, использующий Noda Time 1.x, работает нормально. Возникло следующее исключение:
Правила безопасности наследования нарушены при переопределении члена: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Доступность с точки зрения безопасности переопределяемого метода должна соответствовать доступности с точки зрения безопасности переопределяемого метода.
Я сузил это до целевой платформы: 1.x нацелен на .NET 3.5 (профиль клиента); 2.x нацелен на .NET 4.5. У них большие различия в плане поддержки PCL и .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(@"..\..\..\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);
}
}
- Создайте еще один проект под названием «UntrustedCode». Это должен быть проект библиотеки классов классического рабочего стола.
- Подпишите сборку; вы можете использовать новый ключ или тот же, что и для CodeRunner. (Это частично для имитации ситуации с Noda Time, а частично для того, чтобы сделать анализ кода счастливым.)
- Вставьте следующий код
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 возникает следующее исключение (переформатировано для удобства чтения):
Необработанное исключение: System.Reflection.TargetInvocationException:
исключение было выброшено целью вызова.
--->
System.TypeLoadException:
правила безопасности наследования нарушены при переопределении члена:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
Доступность с точки зрения безопасности переопределяемого метода должна соответствовать
доступности с точки зрения безопасности переопределяемого метода.
Закомментированные атрибуты показывают то, что я пробовал:
SecurityPermission
рекомендуется двумя разными статьями MS ( первая , вторая ), хотя, что интересно, они делают разные вещи в отношении явной / неявной реализации интерфейсаSecurityCritical
это то, что сейчас есть у Noda Time, и это то, что предлагает ответ на этот вопросSecuritySafeCritical
несколько подсказывают сообщения правил анализа кода- Без каких-либо атрибутов правила анализа кода удовлетворены - с любым из них
SecurityPermission
или при ихSecurityCritical
наличии, правила предписывают вам удалить атрибуты - если у вас их нетAllowPartiallyTrustedCallers
. Следование предложениям в любом случае не помогает. - Noda Time
AllowPartiallyTrustedCallers
применил к нему; приведенный здесь пример не работает ни с примененным атрибутом, ни без него.
Код запускается без исключения, если я добавляю [assembly: SecurityRules(SecurityRuleSet.Level1)]
в UntrustedCode
сборку (и раскомментирую AllowPartiallyTrustedCallers
атрибут), но я считаю, что это плохое решение проблемы, которая может затруднить работу другого кода.
Я полностью признаю, что сильно заблудился, когда дело доходит до такого рода аспектов безопасности .NET. Так что можно сделать , чтобы обеспечить работу с .NET 4.5 и все же позволяют мои типы реализовать ISerializable
и по- прежнему использоваться в средах , таких как .NET Fiddle?
(Хотя я нацелен на .NET 4.5, я считаю, что проблема была вызвана изменениями политики безопасности .NET 4.0, отсюда и тег.)
источник
AllowPartiallyTrustedCallers
должно помочь, но, похоже, это не имеет значения,Ответы:
Согласно MSDN , в .NET 4.0 в основном вы не должны использовать
ISerializable
частично доверенный код, вместо этого вы должны использовать ISafeSerializationDataЦитата из https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Так что, вероятно, это не то, что вы хотели услышать, если вам это нужно, но я не думаю, что есть какой-то способ обойти это, продолжая использовать
ISerializable
(кроме возврата кLevel1
безопасности, которую вы сказали, что не хотите).PS: в
ISafeSerializationData
документации указано, что это только для исключений, но это не кажется таким уж конкретным, вы можете захотеть дать ему шанс ... Я в основном не могу проверить его с помощью вашего образца кода (кроме удаленияISerializable
работ, но вы это уже знали) ... вам нужно посмотреть,ISafeSerializationData
подходит ли вам достаточно.PS2:
SecurityCritical
атрибут не работает, потому что он игнорируется, когда сборка загружается в режиме частичного доверия ( на уровне безопасности 2 ). Вы можете увидеть это на примере кода, если вы отладитьtarget
переменнуюExecuteUntrustedCode
прямо перед вызовом, он будет иметьIsSecurityTransparent
вtrue
иIsSecurityCritical
к ,false
даже если вы отмечаете метод сSecurityCritical
атрибутом)источник
Принятый ответ настолько убедителен, что я почти поверил, что это не ошибка. Но после некоторых экспериментов я могу сказать, что безопасность Level2 - это полный беспорядок; по крайней мере, что-то действительно подозрительное.
Пару дней назад я столкнулся с той же проблемой с моими библиотеками. Я быстро создал модульный тест; однако мне не удалось воспроизвести проблему, с которой я столкнулся в .NET Fiddle, в то время как тот же самый код «успешно» выдал исключение в консольном приложении. В конце концов я нашел два странных способа решить эту проблему.
TL; DR : Оказывается, что если вы используете внутренний тип используемой библиотеки в своем потребительском проекте, то частично доверенный код работает так, как ожидалось: он может создать экземпляр
ISerializable
реализации (а критический для безопасности код не может быть вызван напрямую, но см. ниже). Или, что еще более смешно, вы можете попробовать создать песочницу снова, если она не сработала в первый раз ...Но давайте посмотрим код.
ClassLibrary.dll:
Разделяем два случая: один для обычного класса с критически важным для безопасности контентом и второй вариант
ISerializable
реализации:Один из способов решить эту проблему - использовать внутренний тип из сборки потребителя. Любой тип сделает это; теперь я определяю атрибут:
И соответствующие атрибуты, примененные к сборке:
Подпишите сборку, примените ключ к
InternalsVisibleTo
атрибуту и подготовьтесь к тестированию проекта:UnitTest.dll (использует NUnit и ClassLibrary):
Чтобы использовать внутренний трюк, тестовая сборка также должна быть подписана. Атрибуты сборки:
Примечание : атрибут можно применять где угодно. В моем случае это был метод из случайного тестового класса, на поиск которого у меня ушло несколько дней.
Примечание 2 : Если вы запустите все методы тестирования вместе, может случиться так, что тесты пройдут.
Скелет тестового класса:
И давайте посмотрим тестовые примеры один за другим
Случай 1: реализация ISerializable
Та же проблема, что и в вопросе. Тест пройден, если
InternalTypeReferenceAttribute
применяетсяВ противном случае при создании
Inheritance security rules violated while overriding member...
экземпляра возникает совершенно неприемлемое исключениеSerializableCriticalClass
.Случай 2: Обычный класс с важными для безопасности членами
Тест проходит в тех же условиях, что и первый. Однако здесь проблема совершенно в другом: код с частичным доверием может напрямую обращаться к критически важному элементу безопасности .
Случай 3-4: варианты случая 1-2 с полным доверием
Для полноты картины здесь представлены те же случаи, что и описанные выше, выполненные в полностью доверенном домене. Если вы удалите
[assembly: AllowPartiallyTrustedCallers]
тесты, они не пройдут, потому что тогда вы сможете получить доступ к критическому коду напрямую (поскольку методы больше не прозрачны по умолчанию).Эпилог:
Конечно, это не решит вашу проблему с .NET Fiddle. Но сейчас я был бы очень удивлен, если бы это не ошибка фреймворка.
Самый большой вопрос для меня сейчас - это цитируемая часть принятого ответа. Как они вышли с этой чушью?
ISafeSerializationData
Явно не подходит ни для чего: он используется исключительно базовымException
классом , и если вы подписались наSerializeObjectState
событие (почему не о том , что переопределение метод?), То состояние также будет потреблятьсяException.GetObjectData
в конце концов.AllowPartiallyTrustedCallers
/SecurityCritical
/SecuritySafeCritical
Триумвират атрибутов были разработаны именно для использования указанного выше. Мне кажется полной ерундой, что частично доверенный код не может даже создать экземпляр типа, независимо от попытки использования его критически важных для безопасности элементов. Но это еще большая ерунда (на самом деле дыра в безопасности ), что частично доверенный код может напрямую обращаться к критически важному для безопасности методу (см. Случай 2 ), тогда как это запрещено для прозрачных методов даже из полностью доверенного домена.Так что если ваш потребительский проект представляет собой тестовую или другую известную сборку, то внутренний трюк можно использовать отлично. Для .NET Fiddle и других реальных изолированных сред единственное решение - вернуться к прежним версиям,
SecurityRuleSet.Level1
пока это не будет исправлено Microsoft.Обновление: Сообщество разработчиков билет был создан для выпуска.
источник
Согласно MSDN см .:
источник
GetObjectData
явно, но неявно это не помогает.