Perché Linux su x86 utilizzare segmenti diversi per i processi utente e il kernel?
-
14-10-2019 - |
Domanda
Quindi, so che Linux utilizza quattro segmenti predefiniti per un processore x86 (codice del kernel, dati del kernel, codice utente, dati utente), ma tutti hanno la stessa base e il limite (0x00000000 e 0xFFFFF), significa che ogni mappe segmento allo stesso insieme di indirizzi lineari.
Dato questo, perché nemmeno segmenti utente / kernel? Capisco il motivo per cui ci dovrebbe essere segmenti separati per il codice e dati (solo a causa di come le offerte dei processori x86 con i registri CS e DS), ma perché non hanno un segmento di codice singolo e un singolo segmento di dati? Protezione della memoria avviene attraverso paging, ei segmenti di utenza e del kernel map agli stessi indirizzi lineari in ogni caso.
Soluzione
L'architettura x86 associa un tipo e un livello di privilegio con ogni descrittore segmento. Il tipo di un descrittore permette segmenti da apportare sola lettura, lettura / scrittura, eseguibili, ecc, ma il motivo principale per i diversi segmenti aventi la medesima base e il limite è di consentire un diverso livello di privilegio descrittore (DPL) da utilizzare.
Il DPL è due bit, consentendo i valori da 0 a 3 da codificare. Quando il livello di privilegio è 0, allora si dice che sia anello 0 , che è la più privilegiata. I descrittori di segmento per il kernel di Linux sono l'anello 0, mentre i descrittori di segmento per lo spazio utente sono ring 3 (almeno privilegiato). Questo è vero per la maggior parte dei sistemi operativi segmentati; il nucleo del sistema operativo è anello 0 e il resto è anello 3.
set di The Linux Kernel fino, come lei ha ricordato, quattro segmenti:
- __ KERNEL_CS (segmento di codice del kernel, base = 0, = limite 4GB, tipo = 10, DPL = 0)
- __ KERNEL_DS (segmento di dati Kernel, base = 0, = limite 4GB, tipo = 2, DPL = 0)
- __ USER_CS (segmento di codice utente, base = 0, il limite = 4 GB, type = 10, DPL = 3)
- __ user_ds (segmento di dati utente, base = 0, il limite = 4 GB, type = 2, DPL = 3)
La base e il limite di tutti e quattro sono le stesse, ma i segmenti kernel sono DPL 0, i segmenti utente sono DPL 3, i segmenti di codice sono eseguibili e leggibile (non scrivibile), ed i segmenti di dati sono leggibili e scrivibili ( non eseguibile).
Vedi anche:
Altri suggerimenti
L'architettura di gestione della memoria x86 utilizza sia segmentazione e paginazione. Molto grosso modo, un segmento è una partizione di spazio di indirizzamento di un processo che ha una propria politica di protezione. Così, nell'architettura x86, è possibile suddividere l'intervallo di indirizzi di memoria che un processo vede in più segmenti contigui, e assegnare diverse modalità di protezione a ciascuno. Paging è una tecnica per la mappatura di piccole dimensioni (di solito 4KB) regioni di spazio indirizzo di un processo per pezzi di vera e propria, la memoria fisica. Paging controlla quindi come limitata all'interno di un segmento vengono mappati sui RAM fisica.
Tutti i processi hanno due segmenti:
-
un segmento (indirizzi 0x00000000 attraverso 0xBFFFFFFF) per i dati a livello utente, processi specifici come codice del programma, dati statici, mucchio, e pila. Ogni processo ha la sua, segmento di utenti indipendenti.
-
Un segmento (indirizzi 0xC0000000 attraverso 0xFFFFFFFF), che contiene i dati kernel-specifici come ad esempio le istruzioni del kernel, dati, alcune pile su cui il codice del kernel in grado di eseguire, e più interessante, una regione in questo segmento è direttamente mappati alla memoria fisica, in modo che il kernel può accedere direttamente a locazioni di memoria fisiche, senza doversi preoccupare di traduzione degli indirizzi. Lo stesso segmento kernel è mappato in ogni processo, ma i processi può accedere solo durante l'esecuzione in modalità kernel protetta.
Quindi, in modalità utente, il processo può solo indirizzi di accesso meno 0xC0000000; qualsiasi accesso a un indirizzo superiore a questo si traduce in un errore. Tuttavia, quando un processo in modalità utente inizia ad eseguire nel kernel (per esempio, dopo aver effettuato una chiamata di sistema), il bit di protezione nella CPU passa alla modalità supervisore (e alcuni registri segmentazione sono cambiati), significa che il processo è quindi in grado di accedere agli indirizzi sopra 0xC0000000.
vedi Ed da: qui
in X86 - registri di segmento Linux sono utilizzati per il controllo del buffer [vedere il frammento di codice di seguito, definiti alcuni array char in 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]);
}
Ora, se vediamo i dis-assemblaggio del codice gcc-codice generato.
Dump di codice assembler per la funzione 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
Ora, se togliamo gli array char pila base da questa funzione, gcc non genererà questo controllo guardia.
ho visto lo stesso generato da gcc anche per i moduli del kernel. Fondamentalmente io stavo vedendo un incidente mentre botrapping po 'di codice del kernel ed è stato provocato l'errore con l'indirizzo 0x28 virtuale. Più tardi ho pensato che pensavo di aver inizializzato lo stack pointer in modo corretto e caricato correttamente il programma, io non sto avendo le voci giuste GDT, che si tradurrebbe i fs based di offset in un indirizzo virtuale valido.
Tuttavia, in caso di codice del kernel è stato semplicemente ignorato, l'errore invece di saltare a qualcosa come __stack_chk_fail @ plt>.
L'opzione del compilatore pertinenti che aggiunge questa guardia in gcc è -fstack-protettore. Penso che questa è abilitata di default, che la compilazione di un'applicazione utente.
Per i kernel, si può abilitare questo flag gcc opzione di configurazione CC_STACKPROTECTOR via.
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.
Il file del kernel pertinenti laddove ciò gs / FS è linux / arch / x86 / include / asm / stackprotector.h
di memoria del kernel non dovrebbe essere leggibile da programmi in esecuzione in spazio utente.
I dati del programma spesso non è eseguibile (DEP, una caratteristica del processore, che aiuta a guardia contro l'esecuzione di un buffer overflow e altri attacchi dannosi).
E 'tutta una questione di controllo di accesso - diversi segmenti hanno diritti diversi. Questo è il motivo di accesso a segmento sbagliato vi darà una "segmentation fault".