x86-64 access-violation pointer-arithmetic aslr

x86 64 - Abordar la forma canónica y la aritmética del puntero



x86-64 access-violation (1)

Las reglas de direcciones canónicas significan que hay un agujero gigante en el espacio de direcciones virtuales de 64 bits. 2 ^ 47-1 no es contiguo con la siguiente dirección válida encima, por lo que un solo mmap no incluirá ninguno de los rangos inutilizables de direcciones de 64 bits.

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

En otras palabras:

¿Existe alguna garantía por parte del sistema operativo de que nunca se le asignará memoria cuyo rango de direcciones no varíe en el bit 47?

Sí. El espacio de direcciones de 48 bits soportado por el hardware actual es un detalle de implementación. Las reglas de dirección canónica aseguran que los sistemas futuros puedan admitir más bits de dirección virtual sin romper la compatibilidad con versiones anteriores en ningún grado significativo. Solo necesitaría un indicador de compatibilidad para que el sistema operativo no le dé al proceso ninguna región de memoria con bits altos, no todos iguales. El hardware futuro no necesitará soportar ningún tipo de indicador para ignorar los bits de alta dirección o no, porque la basura en los bits altos es actualmente un error.

Dato curioso: Linux por defecto asigna la pila en la parte superior del rango inferior de direcciones válidas.

p.ej

$ 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

En arquitecturas compatibles con AMD64, las direcciones deben estar en forma canónica antes de ser desreferenciadas.

Del manual de Intel, sección 3.3.7.1 :

En el modo de 64 bits, se considera que una dirección está en forma canónica si los bits de dirección 63 hasta el bit implementado más significativo por la microarquitectura se establecen en todos o en todos los ceros.

Ahora, el bit implementado más significativo en los sistemas operativos y arquitecturas actuales es el bit 47. Esto nos deja con un espacio de direcciones de 48 bits.

Especialmente cuando ASLR está habilitado, los programas de usuario pueden esperar recibir una dirección con el conjunto de 47 bits.

Si se utilizan optimizaciones como el etiquetado de puntero y los bits superiores se utilizan para almacenar información, el programa debe asegurarse de que los bits 48 a 63 se vuelvan a establecer en lo que sea el bit 47 antes de desreferenciar la dirección.

Pero considere este código:

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

Ahora considere que intArray es, digamos:

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

Después de configurarlo en intArray y aumentarlo una vez, y considerando sizeof(int) == 4 , se convertirá en:

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

El bit 47 está en negrita. Lo que sucede aquí es que el segundo puntero recuperado por la aritmética del puntero no es válido porque no está en forma canónica. La dirección correcta debe ser:

1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

¿Cómo manejan los programas esto? ¿Existe alguna garantía por parte del sistema operativo de que nunca se le asignará memoria cuyo rango de direcciones no varíe en el bit 47?