web-dev-qa-db-fra.com

Que sont Ring 0 et Ring 3 dans le contexte des systèmes d'exploitation?

J'ai appris les bases du développement de pilotes sous Windows. Je continue de trouver les termes Ring et Ring. À quoi se réfèrent-ils? S'agit-il de la même chose que mode noya et mode utilisateur?

31

Présentation de l'utilisation de l'anneau Linux x86

Comprendre comment les anneaux sont utilisés sous Linux vous donnera une bonne idée de leur destination.

En mode protégé x86, le CPU est toujours dans l'un des 4 anneaux. Le noyau Linux utilise uniquement 0 et 3:

  • 0 pour le noyau
  • 3 pour les utilisateurs

Il s'agit de la définition la plus dure et la plus rapide du noyau par rapport à l'espace utilisateur.

Pourquoi Linux n'utilise pas les anneaux 1 et 2: Anneaux de privilège CPU: Pourquoi les anneaux 1 et 2 ne sont pas utilisés?

Comment est déterminé l'anneau actuel?

L'anneau actuel est sélectionné par une combinaison de:

  • table de descripteurs globaux: une table en mémoire des entrées GDT, et chaque entrée a un champ Privl qui code l'anneau.

    L'instruction LGDT définit l'adresse sur la table de descripteurs actuelle.

    Voir aussi: http://wiki.osdev.org/Global_Descriptor_Table

  • le segment enregistre CS, DS, etc., qui pointent vers l'index d'une entrée dans le GDT.

    Par exemple, CS = 0 signifie que la première entrée du GDT est actuellement active pour le code d'exécution.

Que peut faire chaque bague?

La puce CPU est physiquement construite de sorte que:

  • l'anneau 0 peut tout faire

  • l'anneau 3 ne peut pas exécuter plusieurs instructions et écrire dans plusieurs registres, notamment:

    • ne peut pas changer sa propre bague! Sinon, il pourrait se mettre à sonner 0 et les sonneries seraient inutiles.

      En d'autres termes, ne peut pas modifier le courant descripteur de segment , qui détermine l'anneau actuel.

    • ne peut pas modifier les tables de pages: Comment fonctionne la pagination x86?

      En d'autres termes, ne peut pas modifier le registre CR3 et la pagination elle-même empêche la modification des tables de pages.

      Cela empêche un processus de voir la mémoire des autres processus pour des raisons de sécurité/facilité de programmation.

    • impossible d'enregistrer les gestionnaires d'interruption. Ceux-ci sont configurés en écrivant dans des emplacements de mémoire, ce qui est également empêché par la pagination.

      Les gestionnaires s'exécutent dans l'anneau 0 et briseraient le modèle de sécurité.

      En d'autres termes, ne peut pas utiliser les instructions LGDT et LIDT.

    • ne peut pas faire IO instructions comme in et out, et a donc des accès matériels arbitraires.

      Sinon, par exemple, les autorisations de fichiers seraient inutiles si un programme pouvait lire directement à partir du disque.

      Plus précisément grâce à Michael Petch : il est en fait possible pour le système d'exploitation d'autoriser IO instructions sur l'anneau 3, ceci est en fait contrôlé par le état de la tâche segment .

      Ce qui n'est pas possible, c'est que l'anneau 3 se donne la permission de le faire s'il ne l'avait pas en premier lieu.

      Linux l'interdit toujours. Voir aussi: Pourquoi Linux n'utilise-t-il pas le changement de contexte matériel via le TSS?

Comment les programmes et les systèmes d'exploitation font-ils la transition entre les anneaux?

  • lorsque le CPU est allumé, il commence à exécuter le programme initial dans l'anneau 0 (en quelque sorte, mais c'est une bonne approximation). Vous pouvez penser que ce programme initial est le noyau (mais c'est normalement un chargeur de démarrage qui appelle ensuite le noyau toujours dans l'anneau 0).

  • lorsqu'un processus utilisateur souhaite que le noyau fasse quelque chose pour lui comme écrire dans un fichier, il utilise une instruction qui génère une interruption telle que int 0x80 ou syscall pour signaler le noyau. x86-64 Linux syscall hello world exemple:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    compiler et exécuter:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub en amont .

    Lorsque cela se produit, le CPU appelle un gestionnaire de rappel d'interruption que le noyau a enregistré au démarrage. Voici un exemple de baremetal concret qui enregistre un gestionnaire et l'utilise .

    Ce gestionnaire s'exécute dans l'anneau 0, qui décide si le noyau autorisera cette action, effectuera l'action et redémarrera le programme userland dans l'anneau 3. x86_64

  • lorsque l'appel système exec est utilisé (ou lorsque le noyau démarre /init ), le noyau prépare les registres et la mémoire du nouveau processus de l'espace utilisateur, puis il saute au point d'entrée et bascule le CPU vers l'anneau 3

  • Si le programme essaie de faire quelque chose de méchant comme écrire dans un registre ou une adresse mémoire interdit (à cause de la pagination), le CPU appelle également un gestionnaire de rappel du noyau dans l'anneau 0.

    Mais comme l'espace utilisateur était méchant, le noyau pourrait cette fois tuer le processus ou lui donner un avertissement avec un signal.

  • Lorsque le noyau démarre, il configure une horloge matérielle avec une fréquence fixe, ce qui génère périodiquement des interruptions.

    Cette horloge matérielle génère des interruptions qui exécutent l'anneau 0 et lui permettent de planifier les processus de l'utilisateur à se réveiller.

    De cette façon, la planification peut se produire même si les processus ne font aucun appel système.

Quel est l'intérêt d'avoir plusieurs anneaux?

La séparation du noyau et de l'espace utilisateur présente deux avantages majeurs:

  • il est plus facile de faire des programmes car vous êtes plus certain que l'un n'interférera pas avec l'autre. Par exemple, un processus utilisateur n'a pas à se soucier d'écraser la mémoire d'un autre programme à cause de la pagination, ni de mettre le matériel dans un état invalide pour un autre processus.
  • c'est plus sûr. Par exemple. les autorisations de fichiers et la séparation de la mémoire peuvent empêcher une application de piratage de lire vos données bancaires. Cela suppose, bien sûr, que vous ayez confiance dans le noyau.

Comment jouer avec ça?

J'ai créé une configuration de métal nu qui devrait être un bon moyen de manipuler directement les anneaux: https://github.com/cirosantilli/x86-bare-metal-examples

Je n'ai pas eu la patience de faire un exemple de Userland malheureusement, mais je suis allé jusqu'à la configuration de la pagination, donc le Userland devrait être faisable. J'adorerais voir une demande de tirage.

Alternativement, les modules du noyau Linux s'exécutent dans l'anneau 0, vous pouvez donc les utiliser pour essayer des opérations privilégiées, par ex. lire les registres de contrôle: Comment accéder aux registres de contrôle cr0, cr2, cr3 à partir d'un programme? Obtenir un défaut de segmentation

Voici un configuration QEMU + Buildroot pratique pour l'essayer sans tuer votre hôte.

L'inconvénient des modules du noyau est que d'autres kthreads sont en cours d'exécution et pourraient interférer avec vos expériences. Mais en théorie, vous pouvez prendre en charge tous les gestionnaires d'interruption avec votre module noyau et posséder le système, ce serait un projet intéressant en fait.

Anneaux négatifs

Bien que les anneaux négatifs ne soient pas réellement référencés dans le manuel d'Intel, il existe en fait des modes CPU qui ont des capacités supplémentaires que l'anneau 0 lui-même, et conviennent donc parfaitement au nom "anneau négatif".

Un exemple est le mode hyperviseur utilisé dans la virtualisation.

Pour plus de détails, voir: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1

[~ # ~] bras [~ # ~]

Dans ARM, les anneaux sont plutôt appelés niveaux d'exception, mais les idées principales restent les mêmes.

Il existe 4 niveaux d'exception dans ARMv8, couramment utilisés comme:

  • EL0: espace utilisateur

  • EL1: noyau ("superviseur" dans ARM).

    Entré avec l'instruction svc (SuperVisor Call), précédemment connue sous le nom de swiavant assemblage unifié , qui est l'instruction utilisée pour effectuer des appels système Linux. Exemple ARMv8 de Hello World:

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub en amont .

    Testez-le avec QEMU sur Ubuntu 16.04:

    Sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Voici un exemple concret de baremetal qui enregistre un gestionnaire SVC et effectue un appel SVC .

  • EL2: hyperviseurs , par exemple Xen .

    Entré avec l'instruction hvc (appel HyperVisor).

    Un hyperviseur est à un OS, ce qu'est un OS à userland.

    Par exemple, Xen vous permet d'exécuter plusieurs systèmes d'exploitation tels que Linux ou Windows sur le même système en même temps, et il isole les systèmes d'exploitation les uns des autres pour la sécurité et la facilité de débogage, tout comme Linux le fait pour les programmes utilisateur.

    Les hyperviseurs sont un élément clé de l'infrastructure cloud d'aujourd'hui: ils permettent à plusieurs serveurs de fonctionner sur un même matériel, en gardant une utilisation du matériel toujours proche de 100% et en économisant beaucoup d'argent.

    AWS, par exemple, a utilisé Xen jusqu'en 2017 lorsque son passage à KVM a fait les nouvelles .

  • EL3: encore un autre niveau. Exemple TODO.

    Entré avec l'instruction smc (appel en mode sécurisé)

ARMv8 Architecture Reference Model DDI 0487C.a - Chapter D1 - The AArch64 System Level Programmer's Model - Figure D1-1 illustre cela magnifiquement:

enter image description here

Notez comment ARM, peut-être en raison de l'avantage du recul, a une meilleure convention de dénomination pour les niveaux de privilège que x86, sans avoir besoin de niveaux négatifs: 0 étant le plus bas et 3 le plus élevé. Les niveaux supérieurs ont tendance à être créés plus souvent que les niveaux inférieurs.

L'EL actuel peut être interrogé avec l'instruction MRS: quel est le mode d'exécution actuel/niveau d'exception, etc?

ARM ne nécessite pas la présence de tous les niveaux d'exception pour permettre les implémentations qui n'ont pas besoin de la fonctionnalité pour enregistrer la zone de la puce. ARMv8 "Niveaux d'exception" dit:

Une implémentation peut ne pas inclure tous les niveaux d'exception. Toutes les implémentations doivent inclure EL0 et EL1. EL2 et EL3 sont facultatifs.

Par exemple, QEMU par défaut est EL1, mais EL2 et EL3 peuvent être activés avec les options de ligne de commande: qemu-system-aarch64 entrant el1 lors de l'émulation de la mise sous tension a5

Extraits de code testés sur Ubuntu 18.10.

Les processeurs Intel (x86 et autres) accordent aux applications des pouvoirs limités. Pour restreindre (protéger) les ressources critiques comme les E/S, la mémoire, les ports, etc., le processeur en liaison avec le système d'exploitation (Windows dans ce cas) fournit des niveaux de privilège (0 étant le plus de privilèges à 3 le moins) qui correspondent respectivement au mode noyau et au mode utilisateur.

Ainsi, le système d'exploitation exécute le code du noyau dans l'anneau 0 - le niveau de privilège le plus élevé (sur 0) fourni par le processeur - et le code utilisateur dans l'anneau 3.

Pour plus de détails, voir http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection/

12
Ravi Tiwari

Eh bien, c'est une question assez large. Cependant, vous pouvez utiliser Google ou simplement lire les articles sur Wikipedia à propos de ces choses pour obtenir un premier aperçu.

2
pzaenger