Pourquoi Linux sur x86 utilisent différents segments pour les processus utilisateur et le noyau?

StackOverflow https://stackoverflow.com/questions/4575032

Question

Alors, je sais que Linux utilise quatre segments par défaut pour un processeur x86 (code du noyau, les données du noyau, code utilisateur, les données utilisateur), mais ils ont tous la même base et la limite (0x00000000 et 0xfffff), ce qui signifie chaque cartes du segment pour le même ensemble d'adresses linéaires.

Compte tenu de cela, pourquoi ont même des segments utilisateur / noyau? Je comprends pourquoi il devrait y avoir des segments séparés pour le code et les données (seulement en raison de la façon dont le processeur x86 avec les registres et cs ds), mais pourquoi pas un segment de code unique et un segment de données unique? Protection de la mémoire se fait par la pagination, et les utilisateurs et les segments noyau carte aux mêmes adresses linéaires de toute façon.

Était-ce utile?

La solution

L'architecture x86 associe un type et un niveau de privilège à chaque descripteur de segment. Le type d'un descripteur permet des segments d'être en lecture seule, lecture / écriture, exécutable, etc., mais la principale raison de différents segments ayant la même base et la limite est de permettre à utiliser un niveau de privilège de descripteur différent (DPL).

Le PPD a deux bits, ce qui permet des valeurs de 0 à 3 à coder. Lorsque le niveau de privilège est 0, alors il est dit 0 , qui est le plus privilégié. Les descripteurs de segment pour le noyau Linux sont anneau 0, alors que les descripteurs de segment de l'espace utilisateur sont anneau 3 (le moins privilégié). Cela est vrai pour la plupart des systèmes d'exploitation par secteur; le noyau du système d'exploitation est un cycle 0 et le reste est un cycle 3.

Les jeux de Linux vers le haut, comme vous l'avez mentionné, quatre segments:

  • __ KERNEL_CS (segment de code de noyau, base = 0, = limite de 4 Go, type = 10, DPL = 0)
  • __ KERNEL_DS (segment de données du noyau, base = 0, = limite de 4 Go, type = 2, DPL = 0)
  • __ (USER_CS de segment de code d'utilisateur, base = 0, = limite de 4 Go, type = 10, DPL = 3)
  • __ (USER_DS de segment de données d'utilisateur, base = 0, = limite de 4 Go, type = 2, DPL = 3)

La base et la limite de tous les quatre sont les mêmes, mais les segments de noyau sont DPL 0, les segments de l'utilisateur sont DPL 3, les segments de code sont exécutables et lisible (non modifiable), et les segments de données sont lisibles et inscriptibles ( pas exécutable).

Voir aussi:

Autres conseils

L'architecture de gestion de la mémoire x86 utilise à la fois la segmentation et la pagination. Très grosso modo, un segment est une partition de qui a sa propre politique de protection de l'espace d'adressage d'un processus. Ainsi, dans l'architecture x86, il est possible de diviser la plage d'adresses de mémoire qu'un processus voit dans plusieurs segments contigus et attribuer différents modes de protection à chacun. Paging est une technique pour cartographier les petites (habituellement de 4 Ko) régions de l'espace d'adressage d'un processus de morceaux de réel, la mémoire physique. Paging commande ainsi comment les régions à l'intérieur d'un segment sont mises en correspondance sur la RAM physique.

Tous les processus ont deux segments:

  1. un segment (adresse 0x00000000 par 0xBFFFFFFF) pour les données spécifiques au processus de niveau utilisateur, telles que le code du programme, les données statiques, tas, et la pile. Chaque processus a son propre segment d'utilisateur indépendant.

  2. un segment (adresse 0xC0000000 par 0xFFFFFFFF), qui contient des données spécifiques à noyau tels que les instructions du noyau, des données, des piles sur lequel le code du noyau peut exécuter, et de façon plus intéressante, une région, dans ce segment est directement mis en correspondance à la mémoire physique, de sorte que le noyau peut accéder directement à des emplacements de mémoire physique sans avoir à vous soucier de la traduction d'adresse. Le même segment du noyau est mis en correspondance dans tous les processus, mais les processus ne peut y accéder que lors de l'exécution en mode noyau protégé.

Ainsi, en mode utilisateur, le processus peut que les adresses d'accès à moins de 0xC0000000; tout accès à une adresse plus que cela se traduit par une faute. Cependant, lorsqu'un processus en mode utilisateur commence à exécuter dans le noyau (par exemple, après avoir fait un appel du système), le bit de protection dans la CPU passe en mode superviseur (et certains registres de segmentation sont modifiés), ce qui signifie que le processus est ainsi en mesure d'accéder aux adresses ci-dessus 0xC0000000.

Référencez ed de:

dans X86 - registres de segments de Linux sont utilisés pour le contrôle de débordement de tampon [voir ci-dessous l'extrait de code qui ont défini des matrices de caractère dans la pile]:

static void
printint(int xx, int base, int sgn)
{
    char digits[] = "0123456789ABCDEF";
    char buf[16];
    int i, neg;
    uint x;

    neg = 0;
    if(sgn && xx < 0){
        neg = 1;
        x = -xx;
    } else {
        x = xx;
    }

    i = 0;
    do{
        buf[i++] = digits[x % base];
    }while((x /= base) != 0);
    if(neg)
        buf[i++] = '-';

    while(--i >= 0)
        my_putc(buf[i]);
}

Maintenant, si nous voyons le désassemblage du code généré gcc.

Dump de code assembleur pour fonction printint:

 0x00000000004005a6 <+0>:   push   %rbp
   0x00000000004005a7 <+1>: mov    %rsp,%rbp
   0x00000000004005aa <+4>: sub    $0x50,%rsp
   0x00000000004005ae <+8>: mov    %edi,-0x44(%rbp)


  0x00000000004005b1 <+11>: mov    %esi,-0x48(%rbp)
   0x00000000004005b4 <+14>:    mov    %edx,-0x4c(%rbp)
   0x00000000004005b7 <+17>:    mov    %fs:0x28,%rax  ------> obtaining an 8 byte guard from based on a fixed offset from fs segment register [from the descriptor base in the corresponding gdt entry]
   0x00000000004005c0 <+26>:    mov    %rax,-0x8(%rbp) -----> pushing it as the first local variable on to stack
   0x00000000004005c4 <+30>:    xor    %eax,%eax
   0x00000000004005c6 <+32>:    movl   $0x33323130,-0x20(%rbp)
   0x00000000004005cd <+39>:    movl   $0x37363534,-0x1c(%rbp)
   0x00000000004005d4 <+46>:    movl   $0x42413938,-0x18(%rbp)
   0x00000000004005db <+53>:    movl   $0x46454443,-0x14(%rbp)

...
...
  // function end

   0x0000000000400686 <+224>:   jns    0x40066a <printint+196>
   0x0000000000400688 <+226>:   mov    -0x8(%rbp),%rax -------> verifying if the stack was smashed
   0x000000000040068c <+230>:   xor    %fs:0x28,%rax  --> checking the value on stack is matching the original one based on fs
   0x0000000000400695 <+239>:   je     0x40069c <printint+246>
   0x0000000000400697 <+241>:   callq  0x400460 <__stack_chk_fail@plt>
   0x000000000040069c <+246>:   leaveq 
   0x000000000040069d <+247>:   retq 

Maintenant, si on enlève les tableaux char à base de pile à partir de cette fonction, gcc ne génère pas cette vérification de garde.

J'ai vu le même généré par gcc même pour les modules noyau. Au fond, je voyais un accident alors que botrapping du code du noyau et il a été la formation de failles avec 0x28 d'adresse virtuelle. Plus tard, je pensais que pensais avoir initialisé correctement le pointeur de la pile correctement et chargé le programme, je ne suis pas avoir les entrées à droite, gdt qui se traduirait par le décalage dans fs à base d'une adresse virtuelle valide.

Toutefois, dans le cas du code du noyau, il était tout simplement ignorer l'erreur au lieu de sauter à quelque chose comme __stack_chk_fail @ plt>.

L'option du compilateur correspondant qui ajoute cette garde dans gcc est -fstack-protecteur. Je pense que cela est activé par défaut qui la compilation d'une application utilisateur.

Pour noyau, nous pouvons activer cette option gcc via l'option config CC_STACKPROTECTOR.

config CC_STACKPROTECTOR
 699        bool "Enable -fstack-protector buffer overflow detection (EXPERIMENTAL)"
 700        depends on SUPERH32
 701        help
 702          This option turns on the -fstack-protector GCC feature. This
 703          feature puts, at the beginning of functions, a canary value on
 704          the stack just before the return address, and validates
 705          the value just before actually returning.  Stack based buffer
 706          overflows (that need to overwrite this return address) now also
 707          overwrite the canary, which gets detected and the attack is then
 708          neutralized via a kernel panic.
 709
 710          This feature requires gcc version 4.2 or above.

Le fichier du noyau concerné où ce gs / fs est linux / arch / x86 / include / asm / stackprotector.h

La mémoire du noyau ne doit pas être lisible à partir des programmes en cours d'exécution dans l'espace utilisateur.

Les données du programme est souvent exécutable (DEP, une fonction du processeur, ce qui permet de se prémunir contre l'exécution d'un tampon débordés et d'autres attaques malveillantes).

Il est question de contrôle d'accès - différents segments ont des droits différents. C'est pourquoi un accès au segment mauvais vous donnera une « erreur de segmentation ».

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top