61 votes

Quelles sont les conséquences de demander aux API de Reflection d'écraser System.String.Empty ?

Je suis tombé sur ce code :

static void Main()
{
    typeof(string).GetField("Empty").SetValue(null, "evil");//from DailyWTF

    Console.WriteLine(String.Empty);//check

    //how does it behave?
    if ("evil" == String.Empty) Console.WriteLine("equal"); 

    //output: 
    //evil 
    //equal

 }

et je me demande comment il est même possible de compiler ce morceau de code. Mon raisonnement est le suivant :

Selon MSDN String.Empty est en lecture seule, il devrait donc être impossible de le modifier et la compilation devrait se terminer par le message "A static readonly field cannot be assigned to" ou une erreur similaire.

Je pensais que les assemblages de la bibliothèque des classes de base étaient protégés, signés et ainsi de suite pour empêcher exactement ce genre d'attaque. La prochaine fois, quelqu'un pourra modifier System.Security.Cryptography ou une autre classe critique.

Je pensais que les assemblages de la bibliothèque des classes de base étaient compilés par NGEN après l'installation de .NET. Par conséquent, la modification des champs de la classe String devrait nécessiter un piratage avancé et être beaucoup plus difficile.

Et pourtant ce code compile et fonctionne. Quelqu'un peut-il m'expliquer ce qui ne va pas dans mon raisonnement ?

52voto

Ben Voigt Points 151460

Un champ statique en lecture seule ne peut être attribué à

Vous ne l'assignez pas. Vous appelez des fonctions publiques dans le System.Reflection espace de noms. Il n'y a aucune raison pour que le compilateur se plaigne de cela.

D'ailleurs, typeof(string).GetField("Empty") pourrait utiliser des variables entrées par l'utilisateur à la place, il n'y a pas de moyen sûr pour le compilateur de dire dans tous les cas si l'argument de la fonction GetField finira par être "Empty" .

Je pense que vous voulez Reflection pour voir que le champ est marqué initonly et lancer une erreur au moment de l'exécution. Je peux comprendre pourquoi vous vous attendez à cela, mais pour les tests en boîte blanche, même l'écriture dans le fichier initonly Les champs ont une certaine application.

La raison pour laquelle NGEN n'a aucun effet est que vous ne modifiez pas de code ici, seulement des données. Les données sont stockées en mémoire avec .NET comme avec tout autre langage. Les programmes natifs peuvent utiliser des sections de mémoire en lecture seule pour des choses comme les constantes de chaîne, mais le pointeur vers la chaîne est généralement toujours accessible en écriture et c'est ce qui se passe ici.

Notez que votre code doit être exécuté avec une confiance totale pour utiliser la réflexion de cette manière discutable. De plus, le changement n'affecte qu'un seul programme, ce n'est pas une sorte de vulnérabilité de sécurité comme vous semblez le penser (si vous exécutez du code malveillant dans votre processus avec une confiance totale, cette décision de conception est le problème de sécurité, pas la réflexion).


Notez également que les valeurs de initonly champs intérieurs mscorlib.dll sont des invariants globaux du runtime .NET. Après les avoir brisés, vous ne pouvez même pas tester de manière fiable si l'invariant a été brisé, car le code permettant d'inspecter la valeur actuelle de System.String.Empty a également été brisé, parce que vous avez violé ses invariants. Commencez à violer les invariants du système et vous ne pourrez plus vous fier à rien.

En spécifiant ces valeurs dans les spécifications .NET, le compilateur peut mettre en œuvre toute une série d'optimisations des performances. Un exemple simple est que

s == System.String.Empty

et

(s != null) && (s.Length == 0)

sont équivalentes, mais la seconde est beaucoup plus rapide (relativement parlant).

Le compilateur peut également déterminer que

if (int.Parse(s) > int.MaxValue)

n'est jamais vrai, et génère un saut inconditionnel vers le bloc else (il doit encore appeler Int32.Parse pour avoir le même comportement d'exception, mais la comparaison peut être supprimée).

System.String.Empty est également très utilisé dans les implémentations de la BCL. Si vous l'écrasez, toutes sortes de choses folles peuvent se produire, y compris des dommages qui fuient en dehors de votre programme. (par exemple, vous pouvez écrire dans un fichier dont le nom est construit à l'aide de la manipulation de chaînes de caractères... lorsque la chaîne de caractères se rompt, vous risquez d'écraser le mauvais fichier).


Et le comportement peut facilement varier selon les versions de .NET. Normalement, lorsque de nouvelles possibilités d'optimisation sont trouvées, elles ne sont pas reportées dans les versions précédentes du compilateur JIT (et même si elles l'étaient, il pourrait y avoir des installations antérieures à la mise en œuvre du report). En particulier. String.Empty -Les optimisations liées à .NET sont sensiblement différentes entre .NET 2.x et Mono et .NET 4.5+.

42voto

Eric Lippert Points 300275

Le code se compile parce que chaque ligne du code est un C# parfaitement légal. Quelle ligne spécifique devrait, selon vous, constituer une erreur de syntaxe ? Il n'y a aucune ligne de code qui affecte à un champ en lecture seule. Il y a une ligne de code qui appelle une méthode dans Reflection qui assigne à un champ en lecture seule, mais elle est déjà compilée et, en fin de compte, la chose qui brise la sécurité n'a même pas été écrite en C#, elle a été écrite en C++. C'est une partie du moteur d'exécution lui-même.

Le code s'exécute avec succès car la confiance totale signifie complet confiance . Vous exécutez votre code dans un environnement de confiance totale, et comme la confiance totale signifie complet confiance, le runtime suppose que vous savez ce que vous faites quand vous faites cette chose stupide et dangereuse.

Si vous essayez d'exécuter votre code dans un environnement partiellement sécurisé, vous verrez que Reflection lève une exception " vous n'êtes pas autorisé à faire cela ".

Et oui, les assemblages sont signés et ainsi de suite. Si vous exécutez un code de confiance totale, alors bien sûr, ils peuvent s'amuser avec ces assemblages autant qu'ils le veulent. C'est ce que signifie la confiance totale. Un code partiellement fiable ne peut pas le faire, mais un code totalement fiable peut faire tout ce que vous pouvez faire. N'accordez une confiance totale qu'au code dont vous avez réellement confiance qu'il ne fera pas de folies en votre nom.

20voto

BrunoLM Points 26573

La réflexion vous permet de défier les lois de la physique faire n'importe quoi. Vous pouvez même définir la valeur des membres privés.

La réflexion ne suit pas de règles, vous pouvez le lire sur MSDN .

Un autre exemple : Puis-je modifier un champ privé en lecture seule en C# en utilisant la réflexion ?


Si vous êtes sur une application web, vous pouvez définir l'option le niveau de confiance de votre application.

level="[Full|High|Medium|Low|Minimal]"

Ce sont les restrictions du niveau de confiance, selon MSDN. à la confiance moyenne, vous limitez l'accès à la réflexion.

Edit : NE PAS Exécuter une application web autre que Full Trust, ceci est une recommandation directe de l'équipe ASP.NET. Pour protéger votre application, créez un App Pool pour chaque site web.


Il n'est pas non plus recommandé d'utiliser le reflet pour tout. Elle a le bon endroit et le bon moment pour être utilisée.

5voto

mbx Points 1823

Un point que personne n'a mentionné ici auparavant : Ce morceau de code provoque un comportement différent sur les différentes implémentations/plateformes .net. En fait, sous Mono, il ne renvoie rien du tout : voir IDE One (Mono 2.8) Mono 2.6.7 (linux) produit la même "sortie".

Je n'ai pas encore regardé le code de bas niveau, mais je suppose que c'est spécifique au compilateur comme mentionné par PRASHANT P ou l'environnement d'exécution.

UPDATE

L'exécution de l'exe compilé par Mono sous Windows (MS Dotnet 4) donne les résultats suivants

evil 
equal

L'exécution de l'exe compilé par Windows sur linux n'était pas possible (Dotnet 4...) donc j'ai recompilé avec Dotnet 2 (il est toujours indiqué maléfique y égal sous Windows). Il n'y a "aucune" sortie. Bien sûr, il doit y avoir au moins la "\n" du premier WriteLine en fait, il est là. J'ai envoyé la sortie vers un fichier et j'ai lancé mon éditeur hexadécimal pour regarder un seul caractère. 0x0A .

Pour faire court : il semble que ce soit environnement d'exécution spécifique.

0voto

PRASHANT P Points 702

Readonly est seulement applicable

au niveau du COMPILATEUR

donc

peut être modifié au niveau inférieur actuel

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X