¿Por qué Linux en x86 utilizan diferentes segmentos para los procesos de usuario y el kernel?
-
14-10-2019 - |
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.
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:
-
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.
-
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".