¿Por qué Linux en x86 utilizan diferentes segmentos para los procesos de usuario y el kernel?

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

Pregunta

Por lo tanto, sé que Linux utiliza cuatro segmentos predeterminados para un procesador x86 (código del núcleo, datos del núcleo, código de usuario, datos de usuario), pero todos ellos tienen la misma base y límite (0x00000000 y 0xFFFFF), lo que significa cada uno mapas de segmentos para el mismo conjunto de direcciones lineales.

En vista de esto, ¿por qué siquiera tiene segmentos de usuario / kernel? Yo entiendo por qué debería haber segmentos separados para código y datos (sólo se debe a la forma en las ofertas de procesador x86 con los registros C y D), pero ¿por qué no tener un segmento de código individual y un solo segmento de datos? La protección de memoria se realiza a través de paginación, y los segmentos de usuarios y de almendra mapa a las mismas direcciones lineales de todos modos.

¿Fue útil?

Solución

La arquitectura x86 asocia un tipo y un nivel de privilegio con cada descriptor de segmento. El tipo de un descriptor permite segmentos para hacerse sólo lectura, lectura / escritura, ejecutable, etc., pero la razón principal para los diferentes segmentos que tienen la misma base y límite es permitir un nivel de privilegio descriptor diferente (DPL) que se utilizará.

El DPL es dos bits, permitiendo que los valores de 0 a 3 para codificar. Cuando el nivel de privilegio es 0, entonces se dice que es anillo 0 , que es la más privilegiada. Los descriptores de segmento para el núcleo de Linux son anillo 0 mientras que los descriptores de segmento para el espacio de usuario son anillo de 3 (menos privilegiada). Esto es cierto para la mayoría de los sistemas operativos segmentados; el núcleo del sistema operativo es el anillo 0 y el resto es un anillo de 3.

El Linux del núcleo, el que usted ha mencionado, cuatro segmentos:

  • __ KERNEL_CS (segmento de código Kernel, base = 0, límite = 4 GB, tipo = 10, DPL = 0)
  • __ KERNEL_DS (segmento de datos Kernel, base = 0, límite = 4 GB, tipo = 2, DPL = 0)
  • __ USER_CS (segmento de código de usuario, base = 0, límite = 4 GB, tipo = 10, DPL = 3)
  • __ user_ds (segmento de datos de usuario, base = 0, límite = 4 GB, tipo = 2, DPL = 3)

La base y el límite de los cuatro son los mismos, pero los segmentos del núcleo son DPL 0, los segmentos de usuario son DPL 3, los segmentos de código son ejecutables y legible (no escribible), y los segmentos de datos son de lectura y escritura ( no ejecutable).

Ver también:

Otros consejos

La arquitectura de gestión de memoria x86 utiliza tanto la segmentación y paginación. Muy a grandes rasgos, un segmento es una partición del espacio de direcciones de un proceso que tiene su propia política de protección. Así, en la arquitectura x86, es posible dividir el rango de direcciones de memoria que un proceso ve en múltiples segmentos contiguos, y asignar diferentes modos de protección a cada uno. Paginación es una técnica para mapear pequeños (generalmente de 4 KB) regiones del espacio de direcciones de un proceso de trozos de memoria real, física. así Paging controla la forma en regiones dentro de un segmento son mapeadas a RAM física.

Todos los procesos tienen dos segmentos:

  1. un segmento (direcciones 0x00000000 través 0xBFFFFFFF) para los datos de nivel de usuario, procesos específicos, tales como el código del programa, los datos estáticos, montón, y la pila. Cada proceso tiene su propio segmento de usuario, independiente.

  2. un segmento (direcciones 0xC0000000 través 0xFFFFFFFF), que contiene datos kernel-específicas, tales como las instrucciones del núcleo, datos, algunas pilas en el que el código del núcleo puede ejecutar, y más interesante, una región en este segmento se asigna directamente a la memoria física, por lo que el núcleo pueda acceder directamente a las ubicaciones físicas de la memoria sin tener que preocuparse de traducción de direcciones. El mismo segmento del núcleo está asignada en cada proceso, pero los procesos puede acceder a él sólo cuando se ejecuta en modo de núcleo protegido.

Por lo tanto, en el modo de usuario, el proceso puede sólo las direcciones de acceso de menos de 0xC0000000; cualquier acceso a una dirección mayor que esto resulta en un fallo. Sin embargo, cuando un proceso de modo de usuario comienza a ejecutar en el núcleo (por ejemplo, después de haber hecho una llamada al sistema), el bit de protección en la CPU se cambia a modo supervisor (y algunos registros de segmentación se cambian), lo que significa que el proceso es de esta manera acceder a las direcciones arriba 0xC0000000.

Referir ed de: aquí

en X86 - registros de segmento Linux se utilizan para el registro de desbordamiento del búfer [ver el siguiente fragmento de código que se han definido algunas matrices de carbonilla en la pila]:

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]);
}

Ahora bien, si vemos las desmontaje del código generado gcc-código.

Volcado de código ensamblador para printint función:

 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 

Ahora bien, si eliminamos los arrays de char a base de pila de esta función, gcc no generará esta comprobación guardia.

he visto el mismo generada por gcc incluso para los módulos del núcleo. Básicamente yo estaba viendo un accidente, mientras que algunos botrapping código del núcleo y se critica con 0x28 de direcciones virtuales. Más tarde me di cuenta de que pensaba que había inicializado el puntero de pila correctamente y cargado el programa correctamente, no estoy teniendo las entradas correctas en la GDT, lo que se traduciría los fs based desplazamiento en una dirección virtual válida.

Sin embargo, en el caso del código del kernel simplemente se ignora, el error en vez de saltar a algo así como __stack_chk_fail @ PLT>.

La opción del compilador relevante que añade esta guardia en gcc es -fstack-protector. Creo que esto está activado por defecto, que la compilación de una aplicación de usuario.

Para el kernel, podemos permitir que esta bandera gcc opción de configuración a través de 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.

El archivo de núcleo relevante en este gs / FS es linux / arch / x86 / include / asm / stackprotector.h

memoria del núcleo no debe ser legible de los programas que se ejecutan en el espacio de usuario.

Los datos del programa a menudo no es ejecutable (DEP, una característica de procesador, lo que ayuda a proteger contra la ejecución de un búfer desbordado y otros ataques maliciosos).

Se trata de control de acceso - diferentes segmentos tienen diferentes derechos. Es por eso que el acceso a segmentos mal le dará un "fallo de segmentación".

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top