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).
źródło
AllowPartiallyTrustedCallers
powinno załatwić sprawę, ale nie wydaje się, aby różnicaOdpowiedzi:
Według MSDN w .NET 4.0 w zasadzie nie należy używać
ISerializable
kodu częściowo zaufanego, a zamiast tego należy używać ISafeSerializationDataCytowanie z https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
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:
ISafeSerializationData
dokumentacja 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 usuwaniemISerializable
prac, ale już to wiedziałeś) ... będziesz musiał zobaczyć, czyISafeSerializationData
ci odpowiada.PS2:
SecurityCritical
atrybut 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 debugowanietarget
zmiennej wExecuteUntrustedCode
prawo przed wywołaniem go, to będzie miećIsSecurityTransparent
dotrue
iIsSecurityCritical
dofalse
nawet jeśli oznaczyć metodę zSecurityCritical
atrybutem)źródło
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ć
ISerializable
implementację (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
ISerializable
implementacyjny: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:
Oraz odpowiednie atrybuty zastosowane do zespołu:
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:
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:
Zobaczmy kolejno przypadki testowe
Przypadek 1: implementacja ISerializowalna
Ta sama kwestia, co w pytaniu. Test przechodzi, jeśli
InternalTypeReferenceAttribute
jest stosowanyW przeciwnym razie
Inheritance security rules violated while overriding member...
podczas tworzenia instancji pojawi się całkowicie nieodpowiedni wyjątekSerializableCriticalClass
.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 .
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).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?
ISafeSerializationData
Wyraźnie nie jest rozwiązaniem na wszystko: jest używany wyłącznie przez bazowejException
klasy i jeśli subskrybowaćSerializeObjectState
zdarzenie (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
/SecuritySafeCritical
Triumwirat 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.Level1
czasu, gdy zostanie to naprawione przez firmę Microsoft.Aktualizacja: bilet Społeczność Twórca został stworzony dla tej kwestii.
źródło
Według MSDN patrz:
źródło
GetObjectData
jawnie, ale robienie tego niejawnie nie pomaga.