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 ISerializable
interfejs.
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:
SecurityPermission
jest zalecany przez dwa różne artykuły MS ( pierwszy , drugi ), chociaż, co ciekawe, robią one różne rzeczy wokół jawnej / niejawnej implementacji interfejsuSecurityCritical
jest tym, czym obecnie dysponuje Noda Time i co sugeruje odpowiedź na to pytanieSecuritySafeCritical
jest nieco sugerowane przez komunikaty reguł analizy kodu- Bez żadnych atrybutów, reguły Code Analysis są zadowoleni - z albo
SecurityPermission
czySecurityCritical
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
AllowPartiallyTrustedCallers
zastosował; 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 UntrustedCode
zestawu (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 ISerializable
i 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).
AllowPartiallyTrustedCallers
powinno załatwić sprawę, ale nie wydaje się, aby różnica