Les anneaux de la CPU sont la distinction la plus claire
En mode protégé x86, la CPU est toujours dans l'une des 4 sonneries. Le noyau Linux utilise uniquement 0 et 3:
- 0 pour le noyau
- 3 pour les utilisateurs
C'est la définition la plus dure et la plus rapide du noyau par rapport au pays utilisateur.
Pourquoi Linux n'utilise pas les anneaux 1 et 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
Comment l'anneau actuel est-il déterminé?
La sonnerie actuelle est sélectionnée par une combinaison de:
table de descripteur global: une table en mémoire d'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 descripteur actuelle.
Voir aussi: http://wiki.osdev.org/Global_Descriptor_Table
le segment enregistre CS, DS, etc., qui pointe 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 en cours d’exécution.
Que peut faire chaque anneau?
La puce du processeur est construite physiquement de sorte que:
la bague 0 peut faire n'importe quoi
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, vous ne pouvez pas modifier le descripteur de segment actuel , qui détermine la sonnerie actuelle.
ne peut pas modifier les tables de page: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
En d'autres termes, vous ne pouvez 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 d'autres processus pour des raisons de sécurité / facilité de programmation.
ne peut pas enregistrer les gestionnaires d'interruptions. 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, vous ne pouvez pas utiliser les instructions LGDT et LIDT.
ne peut pas faire d'instructions IO comme in
et out
, et a donc des accès matériels arbitraires.
Autrement, par exemple, les autorisations de fichiers seraient inutiles si un programme pouvait directement lire à partir du disque.
Plus précisément, grâce à Michael Petch : il est en fait possible pour le système d’exploitation d’autoriser les instructions IO sur l’anneau 3, ce contrôle étant effectué par le segment d’état Tâche .
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 eu en premier lieu.
Linux le refuse toujours. Voir aussi: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
Comment les programmes et les systèmes d'exploitation font-ils la transition entre les anneaux?
Lorsque la CPU est allumée, elle lance le programme initial dans l’anneau 0 (bien, 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 veut 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
ousyscall
pour signaler le noyau. x86-64 Linux exemple d'appels Bonjour tout le monde:
.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, la CPU appelle un gestionnaire d’appel d’interruption que le noyau a enregistré au démarrage. Voici un exemple concret de baremetal qui enregistre un gestionnaire et l’utilise .
Ce gestionnaire s'exécute dans l'anneau 0, qui décide si le noyau autorise cette action, effectue l'action et redémarre le programme utilisateur dans l'anneau 3. x86_64
lorsque l' exec
appel système est utilisé (ou lorsque le noyau démarre/init
), le noyau prépare les registres et la mémoire du nouveau processus utilisateur, puis il saute au point d'entrée et fait basculer la CPU vers la sonnerie 3
Si le programme essaie de faire quelque chose de méchant comme écrire dans un registre interdit ou une adresse mémoire (à cause de la pagination), la CPU appelle également un gestionnaire de rappel du noyau dans l'anneau 0.
Mais comme le monde utilisateur était méchant, le noyau pourrait tuer le processus cette fois-ci ou lui donner un avertissement avec un signal.
Lorsque le noyau démarre, il configure une horloge matérielle avec une fréquence fixe, qui génère des interruptions périodiquement.
Cette horloge matérielle génère des interruptions qui exécutent l'anneau 0 et lui permettent de planifier l'activation des processus utilisateur.
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 des noyaux et des utilisateurs présente deux avantages majeurs:
- il est plus facile de créer des programmes car vous êtes plus certain que l'un n'interférera pas avec l'autre. Par exemple, un processus utilisateur ne doit pas craindre d'écraser la mémoire d'un autre programme à cause de la pagination, ni de placer le matériel dans un état non valide pour un autre processus.
- c'est plus sécurisé. Par exemple, les autorisations de fichiers et la séparation de la mémoire pourraient empêcher une application de piratage informatique de lire vos données bancaires. Cela suppose bien sûr que vous faites confiance au noyau.
Comment jouer avec?
J'ai créé une configuration de métal nu qui devrait être un bon moyen de manipuler directement les bagues: https://github.com/cirosantilli/x86-bare-metal-examples
Malheureusement, je n’ai pas eu la patience de donner un exemple d’utilisateur, mais j’ai été aussi loin que la configuration de la pagination, de sorte que l’utilisateur devrait être réalisable. J'aimerais voir une demande de traction.
Les modules du noyau Linux s'exécutent également dans l'anneau 0; vous pouvez donc les utiliser pour tester des opérations privilégiées, par exemple, lire les registres de contrôle: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-d'un-programme-obtenir-segmenta / 7419306 # 7419306
Voici une configuration pratique de QEMU + Buildroot 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 peuvent interférer avec vos expériences. Mais en théorie, vous pouvez prendre en charge tous les gestionnaires d’interruptions avec votre module de noyau et posséder le système, ce serait un projet intéressant.
Anneaux négatifs
Bien que les anneaux négatifs ne soient pas réellement référencés dans le manuel Intel, il existe en réalité des modes de processeur qui ont des capacités supplémentaires par rapport à l’anneau 0 lui-même. Ils 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 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 en tant que:
EL0: pays utilisateur
EL1: noyau ("superviseur" dans la terminologie ARM).
Entré avec l' svc
instruction (SuperVisor Call), auparavant appelée swi
assemblage antérieur , qui est l'instruction utilisée pour passer des appels système Linux. Bonjour exemple du monde ARMv8:
.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' hvc
instruction (appel HyperVisor).
Un hyperviseur est un système d’exploitation, ce qu’est un système d’exploitation.
Par exemple, Xen vous permet d'exécuter simultanément plusieurs systèmes d'exploitation, tels que Linux ou Windows, sur le même système. Il isole les systèmes d'exploitation les uns des autres pour des raisons de sécurité et de débogage, comme le fait Linux pour les programmes utilisateur.
Les hyperviseurs sont un élément clé de l'infrastructure cloud actuelle: ils permettent à plusieurs serveurs de fonctionner sur un seul matériel, ce qui permet de maintenir l'utilisation du matériel à près de 100% et d'économiser beaucoup d'argent.
AWS, par exemple, a utilisé Xen jusqu'en 2017, date à laquelle son passage à KVM a fait la une .
EL3: encore un autre niveau. TODO exemple.
Entré avec l' smc
instruction (appel en mode sécurisé)
Le ARMv8 architecture Modèle de référence DDI 0487C.a - Chapitre D1 - Le modèle de système AArch64 Niveau programmeur - Figure D1-1 illustre cette magnifique:
Notez que ARM, peut-être en raison du recul, a une meilleure convention de dénomination pour les niveaux de privilège que x86, sans nécessiter de niveaux négatifs: 0 étant le plus bas et 3 le plus élevé. Les niveaux les plus élevés ont tendance à être créés plus souvent que les niveaux les plus bas.
Le EL actuel peut être interrogé avec l' MRS
instruction suivante: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc
ARM n'exige pas que tous les niveaux d'exception soient présents pour permettre des implémentations qui n'ont pas besoin de la fonctionnalité pour enregistrer la zone de 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 est défini par défaut sur EL1, mais EL2 et EL3 peuvent être activés avec des options de ligne de commande: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up
Extraits de code testés sur Ubuntu 18.10.