Contexte: Noda Time contient de nombreuses structures sérialisables. Bien que je n'aime pas la sérialisation binaire, nous avons reçu de nombreuses demandes pour la prendre en charge, dans la chronologie 1.x. Nous le soutenons en implémentant l' ISerializable
interface.
Nous avons reçu un rapport récent sur l' échec de Noda Time 2.x dans .NET Fiddle . Le même code utilisant Noda Time 1.x fonctionne correctement. L'exception levée est la suivante:
Règles de sécurité d'héritage violées lors du remplacement du membre: «NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)». L'accessibilité de sécurité de la méthode de substitution doit correspondre à l'accessibilité de sécurité de la méthode remplacée.
J'ai réduit cela au cadre ciblé: 1.x cibles .NET 3.5 (profil client); 2.x cible .NET 4.5. Ils présentent de grandes différences en termes de prise en charge de PCL par rapport à .NET Core et de la structure des fichiers de projet, mais il semble que cela ne soit pas pertinent.
J'ai réussi à reproduire cela dans un projet local, mais je n'ai pas trouvé de solution.
Étapes à suivre pour reproduire dans VS2017:
- Créer une nouvelle solution
- Créez une nouvelle application console Windows classique ciblant .NET 4.5.1. Je l'ai appelé "CodeRunner".
- Dans les propriétés du projet, accédez à Signature et signez l'assembly avec une nouvelle clé. Décochez l'exigence de mot de passe et utilisez n'importe quel nom de fichier de clé.
- Collez le code suivant pour remplacer
Program.cs
. Il s'agit d'une version abrégée du code de cet exemple Microsoft . J'ai gardé tous les chemins identiques, donc si vous voulez revenir au code plus complet, vous ne devriez rien changer d'autre.
Code:
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);
}
}
- Créez un autre projet appelé "UntrustedCode". Il doit s'agir d'un projet de bibliothèque de classes de bureau classique.
- Signez l'assemblée; vous pouvez utiliser une nouvelle clé ou la même que pour CodeRunner. (Ceci est en partie pour imiter la situation Noda Time, et en partie pour garder l'analyse de code heureuse.)
- Collez le code suivant dans
Class1.cs
(en écrasant ce qu'il y a):
Code:
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();
}
}
}
L'exécution du projet CodeRunner donne l'exception suivante (reformatée pour plus de lisibilité):
Exception non gérée: System.Reflection.TargetInvocationException: une
exception a été levée par la cible d'un appel.
--->
System.TypeLoadException:
règles de sécurité d'héritage violées lors
du remplacement du membre: 'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
L'accessibilité de sécurité
de la méthode de substitution doit correspondre à l' accessibilité de sécurité de la méthode remplacée.
Les attributs commentés montrent des choses que j'ai essayées:
SecurityPermission
est recommandé par deux articles MS différents ( premier , deuxième ), bien qu'il soit intéressant de noter qu'ils font des choses différentes autour de l'implémentation d'interface explicite / impliciteSecurityCritical
est ce que Noda Time a actuellement, et c'est ce que la réponse à cette question suggèreSecuritySafeCritical
est quelque peu suggéré par les messages de règle d'analyse du code- Sans aucun attributs, code des règles d' analyse sont heureux - soit avec
SecurityPermission
ouSecurityCritical
présent, les règles que vous disent de supprimer les attributs - à moins que vous n'avez . Suivre les suggestions dans les deux cas n'aide pas.AllowPartiallyTrustedCallers
- Noda Time s'y est
AllowPartiallyTrustedCallers
appliqué; l'exemple ici ne fonctionne pas avec ou sans l'attribut appliqué.
Le code fonctionne sans exception si j'ajoute [assembly: SecurityRules(SecurityRuleSet.Level1)]
à l' UntrustedCode
assembly (et décommente l' AllowPartiallyTrustedCallers
attribut), mais je pense que c'est une mauvaise solution au problème qui pourrait gêner d'autres codes.
J'avoue pleinement être assez perdu en ce qui concerne ce genre d'aspect de sécurité de .NET. Alors, que puis- je faire pour cibler .NET 4.5 tout en autorisant mes types à implémenter ISerializable
et à être encore utilisés dans des environnements tels que .NET Fiddle?
(Bien que je cible .NET 4.5, je pense que ce sont les changements de politique de sécurité .NET 4.0 qui ont causé le problème, d'où la balise.)
la source
AllowPartiallyTrustedCallers
devrait faire l'affaire, mais cela ne semble pas faire de différenceRéponses:
Selon le MSDN , dans .NET 4.0, vous ne devez essentiellement pas utiliser
ISerializable
pour du code partiellement approuvé, mais à la place, vous devez utiliser ISafeSerializationDataCitant de https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Donc probablement pas ce que vous vouliez entendre si vous en avez besoin, mais je ne pense pas qu'il y ait un moyen de contourner cela tout en
ISerializable
continuant à utiliser (à part revenir à laLevel1
sécurité, ce que vous avez dit que vous ne voulez pas).PS: la
ISafeSerializationData
documentation indique que ce n'est que pour des exceptions, mais cela ne semble pas si spécifique, vous voudrez peut-être essayer ... Je ne peux fondamentalement pas le tester avec votre exemple de code (à part supprimer desISerializable
œuvres, mais tu le savais déjà) ... tu devras voir siISafeSerializationData
ça te convient assez.PS2: l'
SecurityCritical
attribut ne fonctionne pas car il est ignoré lorsque l'assembly est chargé en mode confiance partielle ( sur la sécurité Level2 ). Vous pouvez le voir sur votre exemple de code, si vous déboguer latarget
variableExecuteUntrustedCode
juste avant d' invoquer, il aurezIsSecurityTransparent
àtrue
etIsSecurityCritical
defalse
même si vous marquez la méthode avec l'SecurityCritical
attribut)la source
La réponse acceptée est si convaincante que j'ai presque cru que ce n'était pas un bug. Mais après avoir fait quelques expériences maintenant, je peux dire que la sécurité Level2 est un désordre complet; au moins, quelque chose est vraiment louche.
Il y a quelques jours, je suis tombé sur le même problème avec mes bibliothèques. J'ai rapidement créé un test unitaire; cependant, je n'ai pas pu reproduire le problème que j'ai rencontré dans .NET Fiddle, alors que le même code "avec succès" a jeté l'exception dans une application console. En fin de compte, j'ai trouvé deux façons étranges de surmonter le problème.
TL; DR : Il s'avère que si vous utilisez un type interne de la bibliothèque utilisée dans votre projet consommateur, alors le code partiellement fiable fonctionne comme prévu: il est capable d'instancier une
ISerializable
implémentation (et un code critique de sécurité ne peut pas être appelé directement, mais voir ci-dessous). Ou, ce qui est encore plus ridicule, vous pouvez essayer de créer à nouveau le bac à sable s'il n'a pas fonctionné pour la première fois ...Mais voyons du code.
ClassLibrary.dll:
Séparons deux cas: un pour une classe régulière avec un contenu critique pour la sécurité et une
ISerializable
implémentation:Une façon de résoudre le problème consiste à utiliser un type interne de l'assemblage consommateur. N'importe quel type le fera; maintenant je définis un attribut:
Et les attributs pertinents appliqués à l'assemblage:
Signez l'assemblage, appliquez la clé à l'
InternalsVisibleTo
attribut et préparez-vous au projet de test:UnitTest.dll (utilise NUnit et ClassLibrary):
Pour utiliser l'astuce interne, l'assembly de test doit également être signé. Attributs d'assemblage:
Remarque : l'attribut peut être appliqué n'importe où. Dans mon cas, c'était sur une méthode dans une classe de test aléatoire qui m'a pris quelques jours à trouver.
Remarque 2 : Si vous exécutez toutes les méthodes de test ensemble, il peut arriver que les tests réussissent.
Le squelette de la classe de test:
Et voyons les cas de test un par un
Cas 1: implémentation ISerializable
Le même problème que dans la question. Le test réussit si
InternalTypeReferenceAttribute
est appliquéSinon, il y a une
Inheritance security rules violated while overriding member...
exception totalement inappropriée lorsque vous instanciezSerializableCriticalClass
.Cas 2: cours régulier avec des membres critiques pour la sécurité
Le test passe dans les mêmes conditions que le premier. Cependant, le problème est complètement différent ici: un code partiellement approuvé peut accéder directement à un membre critique de sécurité .
Cas 3-4: Versions de confiance totale du cas 1-2
Par souci d'exhaustivité, voici les mêmes cas que ceux ci-dessus exécutés dans un domaine entièrement fiable. Si vous supprimez
[assembly: AllowPartiallyTrustedCallers]
les tests échouent car vous pouvez accéder directement au code critique (car les méthodes ne sont plus transparentes par défaut).Épilogue:
Bien sûr, cela ne résoudra pas votre problème avec .NET Fiddle. Mais maintenant, je serais très surpris si ce n'était pas un bug dans le framework.
La plus grande question pour moi maintenant est la partie citée dans la réponse acceptée. Comment en sont-ils sortis avec ce non-sens? Le
ISafeSerializationData
n'est clairement pas une solution pour quoi que ce soit: il est utilisé exclusivement par laException
classe de base et si vous souscrivez l'SerializeObjectState
événement (pourquoi n'est-ce pas une méthode remplaçable?), Alors l'état sera également consommé par leException.GetObjectData
à la fin.Le triumvirat
AllowPartiallyTrustedCallers
/SecurityCritical
/SecuritySafeCritical
des attributs a été conçu exactement pour l'usage indiqué ci-dessus. Il me semble totalement absurde qu'un code partiellement fiable ne puisse même pas instancier un type quelle que soit la tentative utilisant ses membres critiques pour la sécurité. Mais il est encore plus absurde (une faille de sécurité en fait) qu'un code partiellement fiable puisse accéder directement à une méthode critique de sécurité (voir le cas 2 ) alors que cela est interdit pour les méthodes transparentes même à partir d'un domaine entièrement fiable.Donc, si votre projet consommateur est un test ou un autre assemblage bien connu, alors l'astuce interne peut être parfaitement utilisée. Pour .NET Fiddle et d'autres environnements en bac à sable réels, la seule solution est de revenir
SecurityRuleSet.Level1
jusqu'à ce que cela soit résolu par Microsoft.Mise à jour: un ticket de la communauté des développeurs a été créé pour le problème.
la source
Selon le MSDN voir:
la source
GetObjectData
explicitement, mais le faire implicitement n'aide pas.