Petit retour sur la sécurité
Par amethyste le Jan 25, 2005 | Dans focus | 3 retours »
Il y a quelques temps Patrick Smacchia a publié un billet pour clarifier certains points délicats au sujet de CAS.
Je souhaiterai poursuivre cette initiative à travers un comportement des attributs de sécurité suffisament subtil pour mériter une chronique.
Comme j'aime les exemples, en voici un très simple:
using System; using System.Security.Permissions; using System.Security; using System.Windows.Forms; namespace SecurityBook { class TestRequest { static void Main(string[] args) { // sortie Console Console.WriteLine("Hello le monde"); // Fenêtre Windows MessageBox.Show("Hello le monde"); } } }
Si vous le lancez il affiche sans problème le message "Hello le monde" à la fois sur la sortie Console et dans une fenêtre Windows.
Maintenant décorons l'assemblage à l'aide de l'attribut de sécurité suivant:
[assembly:FileIOPermission(SecurityAction.RequestOptional, Write=@"C:\")]
La sortie Console fonctionne toujours, par contre on ne voit pas apparaître la fenêtre Windows et une exception de sécurité est levée.
N'est-ce pas étrange? Après tout l'action de sécurité ne concerne que les accès fichiers, or le code ne fait aucun accès fichier. Et puis bon sang, un nom comme RequestOptional ça inspire confiance. Que se passe t'il donc?
Pour essayer d'y voir plus clair il y a plusieurs choses à comprendre.
D'abord comment sont octroyées les permissions?
Pour faire court, .NET évalue la liste des preuves que l'assemblage peut présenter. Par exemple sa zone de sécurité, un nom fort...
Ce jeu de preuves est ensuite présenté à la stratégie de sécurité. Elle en déduit alors l'ensemble des permissions qui seront accordées à l'assemblage. Le point crucial est le suivant:
Ce jeu représente le jeu maximum de permissions qui pourront être accordées.
On peut aussi noter entre parenthèses qu'à ce stade le code n'a pas encore commencé à s'exécuter, il n'est même pas en mémoire. C'est cela qui garantit que bien que la collection des preuves soit accessible en lecture/écriture, le code ne peut absolument rien "bidouiller" pour s'accorder lui-même des permissions supplémentaires.
Si on parle de maximum, cela signifie que cet ensemble peut perdre quelques collaborateurs valeureux.
C'est justement ce à quoi servent les attributs de sécurité Request comme par exemple RequestOptional. Ces attributs peuvent être vus comme un filtre. Toutes les permissions qui ne passent pas à travers sont rejetées.
Que perd t'on avec les attributs Request?
Il sont au nombre de 3 et sont traités par .NET dans l'ordre qui suit:
- RequestRefuse: Là pas de surprise. L'assemblage refuse des permissions, même si elles auraient été accordées volontiers.
- RequestMinimum: On déclare les permissions minimales pour que le code puisse s'exécuter. Si il en manque, une SecurityException est levée et le chargement du code s'arrête là.
- RequestOptional: Le filtre en fait appliqué à ce stade est RequestMinimum + RequestOptional. Là aussi, toutes les permissions qui ne passent pas le filtre sont rejetées.
Ca paraît compliqué, alors quelle est la logique derrière cela?
- En demandant RequestMinimum je dis: voici les permissions minimales nécessaires pour que l'application fonctionne. Les autres permissions ne sont pas nécessaires, inutile de les accorder (principe du moindre privilège) on doit donc les refuser.
- En demandant RequestOptional je dis: si ces permissions me sont accordées, merci bien je saurai en tirer partie. Si une permission n'est pas demandée, puisqu'elle est optionnelle on la considère comme refusée
Dans le cas où l'un de ces attributs n'est pas spécifié, des valeurs par défaut suivantes sont appliquées:
| Action de sécurité | Permission par défaut |
| RequestRefuse | PermissionState.None Aucune permission n'est refusée |
| RequestOptional | PermissionState.FullTrust Essaye d'obtenir toutes les permissions du jeu de permissions |
| RequestMinimum | PermissionState.None Aucune permission minimal a priori |
Avec ces valeurs par défaut, les permissions accordées sont exactement celles reçues de la stratégie de sécurité. Le tableau montre en outre que c'est RequestOptional qui est l'attribut dominant.
Revenons maintenant à notre problème.
Si on se réfère au tableau des valeurs par défaut, dans le cas de notre exemple le filtre est déterminé uniquement par RequestOptional qui ne laisse passer que FileIOPermission.
Cette permission est donc la seule qui sera accordée à l'assemblage.
Seulement si on consulte la documentation de la méthode MessageBox.Show(), on constate qu'il nous faut la permission UIPermissionWindow.SafeSubWindows de UIPermission.
On ne l'a pas.
C'est ce qui explique que l'exécution de notre exemple échoue.
RequestOptional est donc en fait un bon moyen de s'assurer qu'un assemblage ne tourne jamais comme FullTrust en spécifiant explicitement les permissions auxquelles on a droit.
On pourrait également combiner RequestRefuse et RequestMinimum et obtenir le même effet. Il faut voir au cas par cas ce qui est le plus simple.
Pour bien préciser les choses imaginons que l'on puisse appliquer l'un ou l'autre des attributs suivants:
Attribut A1:
[assembly:FileIOPermission(SecurityAction.RequestMinimum, Read=@"c:\"")]
Attribut A2:
[assembly:FileIOPermission(SecurityAction.RequestOptional, Write=@"C:\")]
Nous rappelons que l'assemblage est exécuté en local, il est donc considéré comme FullTrust par les groupes de code si on a conservé la stratégie de sécurité par défaut. Examinons les combinaisons possibles.
| A1 | A2 | Analyse | Fonctionne |
| Oui | Oui | L'assemblage ne dispose que des permissions en lecture/écriture. Il lui manque UIPermission. RequestRefuse est à sa valeur par défaut et donc ne refuse aucune permission qui se présente devant le filtre. | Non |
| Oui | Non | L'assemblage dispose des permissions en lecture. RequestPermission expose son filtre par défaut qui laisse passer toutes les permissions disponibles depuis les groupes de code. L'assemblage bénéficie donc de toutes les permissions accordée par les groupes de code, soit FullTrust.RequestRefuse est à sa valeur par défaut et donc ne refuse aucune permission qui se présente devant le filtre. | Oui |
| Non | Oui | RequestOptional laisse passer toutes les permissions proposées par le groupe de code qui voit l'assemblage comme FullTrust.RequestMinimum ne peut qu'agrandir le filtre.RequestRefuse est à sa valeur par défaut et donc ne refuse aucune permission qui se présente devant le filtre. | Oui |
| Non | Non | Les request sont à leur valeur par défaut.RequestRefuse est à sa valeur par défaut et donc ne refuse aucune permission qui se présente devant le filtre.RequestOptional laisse passer toutes les permissionsRequestMinimum ne demande pas de minimum particulier. Tout se passe comme si on n'avait pas d'attributs de sécurité. | Oui |
Il est possible de faire correspondre les actions de sécurité Request aux jeu de permissions nommées standards à l'aide de l'attribut PermissionSetAttribute.
[assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")]
Par contre, on ne peut pas demander le jeu d'autorisation modifiable Everything, ni un jeu d'autorisation nommé personnalisé.
Visual Studio ne permet malheureusement pas de faire tourner un code dans le contexte de sécurité de son choix (du moins la version 2003 car cela s'améliore avec la 2005). On peut par exemple utiliser PermissionSetAttribute pour cela lors des tests.
Par exemple la déclaration:
[assembly: PermissionSetAttribute(SecurityAction.RequestOptional, Name = "LocalIntranet")]
Fera tourner le code avec les même permissions que s'il se trouvait dans la zone LocalIntranet
Je ne prétend pas que c'est forcément facile à maîtriser, mais j'espère que cette chronique vous aidera à maîtriser les mécanismes subtils qui sont derrière CAS.
18: 25/01/2005
Améthyste
Corrigé une petite erreur le 26/01/2005
3 commentaires
Je me suis longtemps posé quelques questions, qui bien que non existencielles, étaient désespérement rester sans réponses.
Encore merci
Je ne vais pas dire combien de temps j'ai perdu à croire comprendre, à me rendre compte que je ne comprenais toujours pas, à réessayer et à tourner en rond.
Combien de temps vous a-t'il fallu pour clarifier tout cela ?
Microsoft implémente mal certains concepts et nous sommes des milliers à perdre un temps précieux à essayer de leur donner un sens clair.
Encore merci
Laisser un commentaire
| « Une FAQ sur les Winforms (MAJ) | Le bug héréditaire » |