GNU/Linux >> Linux Esercitazione >  >> Linux

Se l'heap è inizializzato a zero per motivi di sicurezza, perché lo stack è semplicemente non inizializzato?

La memoria restituita da malloc() non zero-inizializzato. Non dare mai per scontato che lo sia.

Nel tuo programma di test, è solo un colpo di fortuna:immagino il malloc() ho appena ricevuto un nuovo blocco da mmap() , ma non fare affidamento nemmeno su quello.

Ad esempio, se eseguo il tuo programma sulla mia macchina in questo modo:

$ echo 'void __attribute__((constructor)) p(void){
    void *b = malloc(4444); memset(b, 4, 4444); free(b);
}' | cc -include stdlib.h -include string.h -xc - -shared -o pollute.so

$ LD_PRELOAD=./pollute.so ./your_program
a at 0x7ffd40d3aa60: 1256994848 21891 1256994464 21891 1087613792 32765 0 0
b at 0x55834c75d010: 67372036 67372036 67372036 67372036 67372036 67372036 67372036 67372036

Il tuo secondo esempio è semplicemente esporre un artefatto del malloc implementazione in glibc; se lo fai ripetuto malloc /free con un buffer più grande di 8 byte, vedrai chiaramente che solo i primi 8 byte vengono azzerati, come nel seguente codice di esempio.

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

const size_t n = 4;
const size_t m = 0x10;

int main()
{
    for (size_t i = n; i; --i) {
        int *const p = malloc(m*sizeof(int));
        printf("%p ", p);
        for (size_t j = 0; j < m; ++j) {
            printf("%d:", p[j]);
            ++p[j];
            printf("%d ", p[j]);
        }
        free(p);
        printf("\n");
    }
    return 0;
}

Uscita:

0x55be12864010 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 
0x55be12864010 0:1 0:1 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 
0x55be12864010 0:1 0:1 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 
0x55be12864010 0:1 0:1 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4

Indipendentemente da come viene inizializzato lo stack, non vedrai uno stack incontaminato, perché la libreria C fa una serie di cose prima di chiamare main e toccano la pila.

Con la libreria GNU C, su x86-64, l'esecuzione inizia dal punto di ingresso _start, che chiama __libc_start_main per sistemare le cose, e quest'ultimo finisce per chiamare main . Ma prima di chiamare main , chiama una serie di altre funzioni, che causano la scrittura di vari pezzi di dati nello stack. Il contenuto dello stack non viene cancellato tra le chiamate di funzione, quindi quando entri in main , il tuo stack contiene gli avanzi delle precedenti chiamate di funzione.

Questo spiega solo i risultati che ottieni dallo stack, vedi le altre risposte riguardanti il ​​tuo approccio generale e i tuoi presupposti.


In entrambi i casi, ottieni non inizializzato memoria e non puoi fare alcuna supposizione sul suo contenuto.

Quando il sistema operativo deve assegnare una nuova pagina al tuo processo (che sia per il suo stack o per l'arena utilizzata da malloc() ), garantisce che non esporrà i dati di altri processi; il solito modo per assicurarsene è riempirlo con zeri (ma è ugualmente valido sovrascrivere con qualsiasi altra cosa, inclusa anche una pagina del valore di /dev/urandom - in effetti alcuni debug malloc() le implementazioni scrivono schemi diversi da zero, per rilevare ipotesi errate come la tua).

Se malloc() può soddisfare la richiesta dalla memoria già utilizzata e rilasciata da questo processo, il suo contenuto non verrà cancellato (infatti, la cancellazione non ha nulla a che fare con malloc() e non può essere - deve accadere prima che la memoria sia mappata nel tuo spazio degli indirizzi). Potresti ottenere memoria che è stata precedentemente scritta dal tuo processo/programma (ad es. prima di main() ).

Nel tuo programma di esempio, vedi un malloc() regione che non è stata ancora scritta da questo processo (ovvero è diretta da una nuova pagina) e uno stack in cui è stato scritto (da pre-main() codice nel programma). Se esamini una parte maggiore dello stack, scoprirai che è pieno di zero più in basso (nella sua direzione di crescita).

Se vuoi davvero capire cosa sta succedendo a livello di sistema operativo, ti consiglio di ignorare il livello della libreria C e di interagire utilizzando chiamate di sistema come brk() e mmap() invece.


Linux
  1. Come eseguire il mmap dello stack per la chiamata di sistema clone() su Linux?

  2. Perché è necessario il segmento .bss?

  3. Perché il comando free e dmidecode mostrano valori diversi per la RAM?

  4. BCRYPT - Perché le distribuzioni Linux non lo usano di default?

  5. Perché OOM-Killer non può semplicemente uccidere il processo che richiede troppo?

Perché usare il desktop Pantheon per Linux Elementary OS

Perché amo ancora Alpine per la posta elettronica sul terminale Linux

Slack per la CLI – Slack

Gli 8 migliori telefoni sicuri Linux per la privacy e la sicurezza

Perché mount non rispetta l'opzione di sola lettura per i bind mount?

Perché eseguire named(bind) in chroot è così importante per la sicurezza? O forse non lo è?