Indirizzo forma canonica e aritmetica del puntatore

Su architetture conformi a AMD64, gli indirizzi devono essere in forma canonica prima di essere sottoposti a dereferenziazione.

Dal manuale Intel, sezione 3.3.7.1 :

Nella modalità a 64 bit, un indirizzo è considerato in forma canonica se i bit di indirizzo 63 attraverso il bit implementato più significativo dalla microarchitettura sono impostati su tutti o tutti gli zeri.

Ora, il bit più significativo implementato su sistemi operativi e architetture attuali è il 47 ° bit. Questo ci lascia con uno spazio di indirizzi a 48 bit.

Soprattutto quando ASLR è abilitato, i programmi utente possono aspettarsi di ricevere un indirizzo con il settimo bit impostato.

Se vengono utilizzate ottimizzazioni come la codifica del puntatore e vengono utilizzati i bit superiori per memorizzare le informazioni, il programma deve assicurarsi che i bit dal 48 ° al 63 ° siano impostati su qualunque sia il 47 ° bit prima di debind l’indirizzo.

Ma considera questo codice:

int main() { int* intArray = new int[100]; int* it = intArray; // Fill the array with any value. for (int i = 0; i < 100; i++) { *it = 20; it++; } delete [] intArray; return 0; } 

Ora considera che intArray è, diciamo:

0000 0000 0000 0000 0 111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100

Dopo it impostato su intArray e aumentato una volta, e considerando sizeof(int) == 4 , diventerà:

0000 0000 0000 0000 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

Il 47 ° bit è in grassetto. Quello che succede qui è che il secondo puntatore recuperato dall’aritmetica del puntatore non è valido perché non in forma canonica. L’indirizzo corretto dovrebbe essere:

1111 1111 1111 1111 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

Come si gestiscono i programmi con questo? Esiste una garanzia da parte del sistema operativo che non verrà mai assegnata memoria il cui intervallo di indirizzi non varia dal 47 ° bit?

Le regole canoniche degli indirizzi indicano che c’è un enorme buco nello spazio degli indirizzi virtuali a 64 bit. 2 ^ 47-1 non è contiguo con il successivo indirizzo valido sopra di esso, quindi un singolo mmap non includerà alcun intervallo inutilizzabile di indirizzi a 64 bit.

 +----------+ | 2^64-1 | 0xffffffffffffffff | ... | | 2^64-2^47| 0xffff800000000000 +----------+ | | | unusable | | | +----------+ | 2^47-1 | 0x00007fffffffffff | ... | | 0 | 0x0000000000000000 +----------+ 

In altre parole:

Esiste una garanzia da parte del sistema operativo che non verrà mai assegnata memoria il cui intervallo di indirizzi non varia dal 47 ° bit?

Sì. Lo spazio degli indirizzi a 48 bit supportato dall’hardware corrente è un dettaglio di implementazione. Le regole di indirizzo canonico assicurano che i sistemi futuri possano supportare più bit di indirizzi virtuali senza compromettere la compatibilità a ritroso in misura significativa. Avresti solo bisogno di un flag compat per far sì che il sistema operativo non dia al processo alcuna regione di memoria con bit alti non tutti uguali. L’hardware futuro non avrà bisogno di supportare alcun tipo di flag per ignorare i bit di indirizzo elevato o no, perché la posta indesiderata nei bit alti è attualmente un errore.


Fatto divertente: Linux imposta automaticamente il mapping dello stack nella parte superiore dell’intervallo inferiore di indirizzi validi.

per esempio

 $ gdb /bin/ls ... (gdb) b _start Function "_start" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (_start) pending. (gdb) r Starting program: /bin/ls Breakpoint 1, 0x00007ffff7dd9cd0 in _start () from /lib64/ld-linux-x86-64.so.2 (gdb) p $rsp $1 = (void *) 0x7fffffffd850 (gdb) exit $ calc 2^47-1 0x7fffffffffff